diff --git a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/metadata/eva/RoCrateMetadataAdaptor.java b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/metadata/eva/RoCrateMetadataAdaptor.java index 6bc5d97a..3b156a32 100644 --- a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/metadata/eva/RoCrateMetadataAdaptor.java +++ b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/metadata/eva/RoCrateMetadataAdaptor.java @@ -13,7 +13,9 @@ import uk.ac.ebi.eva.lib.entities.Submission; import uk.ac.ebi.eva.lib.entities.Taxonomy; import uk.ac.ebi.eva.lib.models.rocrate.CommentEntity; -import uk.ac.ebi.eva.lib.models.rocrate.DatasetEntity; +import uk.ac.ebi.eva.lib.models.rocrate.DataCatalogEntity; +import uk.ac.ebi.eva.lib.models.rocrate.MinimalProjectDatasetEntity; +import uk.ac.ebi.eva.lib.models.rocrate.ProjectDatasetEntity; import uk.ac.ebi.eva.lib.models.rocrate.FileEntity; import uk.ac.ebi.eva.lib.models.rocrate.LabProcessEntity; import uk.ac.ebi.eva.lib.models.rocrate.Reference; @@ -86,7 +88,7 @@ public RoCrateMetadata getMetadataByProjectAccession(String accession) { List entities = new ArrayList<>(); List publications = getPublications(project); List additionalProjectProperties = getAdditionalProjectProperties(project); - entities.add(new DatasetEntity(project.getProjectAccession(), project.getTitle(), project.getDescription(), + entities.add(new ProjectDatasetEntity(project.getProjectAccession(), project.getTitle(), project.getDescription(), getFirstSubmissionDate(project), project.getCenterName(), publications, getReferences(analysisRoEntities), getReferences(allFiles), getReferences(additionalProjectProperties))); @@ -100,6 +102,26 @@ public RoCrateMetadata getMetadataByProjectAccession(String accession) { return new RoCrateMetadata(entities); } + public RoCrateMetadata getAllProjects(){ + List projects = projectRepository.findAll(); + // Construct the project-related entities + List projectsRoEntities = new ArrayList<>(); + List allAdditionalProjectProperties = new ArrayList<>(); + for (Project project : projects) { + List additionalProjectProperties = getAdditionalProjectProperties(project); + allAdditionalProjectProperties.addAll(additionalProjectProperties); + projectsRoEntities.add(new MinimalProjectDatasetEntity(project.getProjectAccession(), project.getTitle(), project.getDescription(), + getFirstSubmissionDate(project), getReferences(additionalProjectProperties))); + } + + // Construct the DataCatalogEntity and add all entities to the RO-crate metadata + List entities = new ArrayList<>(); + entities.add(new DataCatalogEntity(getReferences(projectsRoEntities), getMostRecentDatePublished(projectsRoEntities))); + entities.addAll(projectsRoEntities); + entities.addAll(allAdditionalProjectProperties); + return new RoCrateMetadata(entities); + } + private List getPublications(Project project) { return project.getDbXrefs() .stream() @@ -118,6 +140,14 @@ private LocalDate getFirstSubmissionDate(Project project) { .map(Submission::getDate).orElse(null); } + private LocalDate getMostRecentDatePublished(List projectsRoEntities) { + + return projectsRoEntities.stream() + .map(entity -> (MinimalProjectDatasetEntity) entity) + .max(Comparator.comparing(MinimalProjectDatasetEntity::getDatePublished)) + .map(MinimalProjectDatasetEntity::getDatePublished).orElse(null); + } + private List getAdditionalProjectProperties(Project project) { List additionalProperties = new ArrayList<>(); Long taxonomyId = null; @@ -133,11 +163,11 @@ private List getAdditionalProjectProperties(Project project) { scientificName = taxonomy.getScientificName(); } - additionalProperties.add(new CommentEntity("taxonomyId", "" + taxonomyId)); - additionalProperties.add(new CommentEntity("scientificName", scientificName)); - additionalProperties.add(new CommentEntity("scope", project.getScope())); - additionalProperties.add(new CommentEntity("material", project.getMaterial())); - additionalProperties.add(new CommentEntity("sourceType", project.getSourceType())); + additionalProperties.add(new CommentEntity(project.getProjectAccession(), "taxonomyId", "" + taxonomyId)); + additionalProperties.add(new CommentEntity(project.getProjectAccession(), "scientificName", scientificName)); + additionalProperties.add(new CommentEntity(project.getProjectAccession(), "scope", project.getScope())); + additionalProperties.add(new CommentEntity(project.getProjectAccession(), "material", project.getMaterial())); + additionalProperties.add(new CommentEntity(project.getProjectAccession(), "sourceType", project.getSourceType())); return additionalProperties; } diff --git a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/DataCatalogEntity.java b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/DataCatalogEntity.java new file mode 100644 index 00000000..573f9743 --- /dev/null +++ b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/DataCatalogEntity.java @@ -0,0 +1,59 @@ +package uk.ac.ebi.eva.lib.models.rocrate; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; + +import java.time.LocalDate; +import java.util.List; + +public class DataCatalogEntity extends RoCrateEntity { + + private static final String ID = "https://www.ebi.ac.uk/eva/"; + private static final String TYPE = "DataCatalog"; + private static final String IDENTIFIER = "EVA studies"; + private static final String LICENSE = "https://www.ebi.ac.uk/data-protection/privacy-notice/embl-ebi-public-website/"; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private LocalDate datePublished; + + private String license; + + private String identifier; + + @JsonProperty("dataset") + private List projects; + + public DataCatalogEntity() { + } + + public DataCatalogEntity(List projects, LocalDate datePublished) { + super(ID , TYPE); + this.license = LICENSE; + this.identifier = IDENTIFIER; + this.projects = projects; + this.datePublished = datePublished; + } + + public String getIdentifier() { + return identifier; + } + + public List getProjects() { + return projects; + } + + public LocalDate getDatePublished() { + return datePublished; + } + + public String getLicense() { + return license; + } + +} diff --git a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/DatasetEntity.java b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/MinimalProjectDatasetEntity.java similarity index 56% rename from eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/DatasetEntity.java rename to eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/MinimalProjectDatasetEntity.java index cea49a3d..05689523 100644 --- a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/DatasetEntity.java +++ b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/MinimalProjectDatasetEntity.java @@ -10,54 +10,37 @@ import java.time.LocalDate; import java.util.List; -public class DatasetEntity extends RoCrateEntity { +public class MinimalProjectDatasetEntity extends RoCrateEntity { - private String name; + protected String name; - private String description; + protected String description; @JsonDeserialize(using = LocalDateDeserializer.class) @JsonSerialize(using = LocalDateSerializer.class) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") - private LocalDate datePublished; + protected LocalDate datePublished; - private String license; - - @JsonProperty("creator") - private String centerName; - - @JsonProperty("citation") - private List publications; + protected String license; @JsonProperty("identifier") - private String projectAccession; - - @JsonProperty("processSequence") - private List analyses; - - @JsonProperty("hasPart") - private List files; + protected String projectAccession; @JsonProperty("comment") // Project-level properties that aren't in schema.org/Dataset are included as Comments - private List additionalProperties; + protected List additionalProperties; - public DatasetEntity() { + public MinimalProjectDatasetEntity() { } - public DatasetEntity(String accession, String name, String description, LocalDate datePublished, String centerName, - List publications, List analyses, List files, - List additionalProperties) { + public MinimalProjectDatasetEntity(String accession, String name, String description, LocalDate datePublished, + List additionalProperties) { super("https://www.ebi.ac.uk/eva/?eva-study=" + accession, "Dataset"); this.projectAccession = accession; this.name = name; this.description = description; this.datePublished = datePublished; this.license = "https://www.ebi.ac.uk/data-protection/privacy-notice/embl-ebi-public-website/"; - this.centerName = centerName; - this.publications = publications; - this.analyses = analyses; - this.files = files; this.additionalProperties = additionalProperties; } @@ -77,26 +60,10 @@ public String getLicense() { return license; } - public String getCenterName() { - return centerName; - } - - public List getPublications() { - return publications; - } - public String getProjectAccession() { return projectAccession; } - public List getAnalyses() { - return analyses; - } - - public List getFiles() { - return files; - } - public List getAdditionalProperties() { return additionalProperties; } diff --git a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/ProjectDatasetEntity.java b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/ProjectDatasetEntity.java new file mode 100644 index 00000000..85060d1f --- /dev/null +++ b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/ProjectDatasetEntity.java @@ -0,0 +1,52 @@ +package uk.ac.ebi.eva.lib.models.rocrate; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +import java.time.LocalDate; +import java.util.List; + +public class ProjectDatasetEntity extends MinimalProjectDatasetEntity { + + @JsonProperty("creator") + private String centerName; + + @JsonProperty("citation") + private List publications; + + @JsonProperty("processSequence") + private List analyses; + + @JsonProperty("hasPart") + private List files; + + public ProjectDatasetEntity() { + } + + public ProjectDatasetEntity(String accession, String name, String description, LocalDate datePublished, String centerName, + List publications, List analyses, List files, + List additionalProperties) { + super(accession, name, description, datePublished, additionalProperties); + this.centerName = centerName; + this.publications = publications; + this.analyses = analyses; + this.files = files; + } + + public String getCenterName() { + return centerName; + } + + public List getPublications() { + return publications; + } + + public List getAnalyses() { + return analyses; + } + + public List getFiles() { + return files; + } + +} diff --git a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/RoCrateEntity.java b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/RoCrateEntity.java index 4cf50dab..55e0baaf 100644 --- a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/RoCrateEntity.java +++ b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/models/rocrate/RoCrateEntity.java @@ -13,7 +13,8 @@ ) @JsonSubTypes({ @JsonSubTypes.Type(value = CommentEntity.class, name = "Comment"), - @JsonSubTypes.Type(value = DatasetEntity.class, name = "Dataset"), + @JsonSubTypes.Type(value = DataCatalogEntity.class, name = "DataCatalog"), + @JsonSubTypes.Type(value = ProjectDatasetEntity.class, name = "Dataset"), @JsonSubTypes.Type(value = FileEntity.class, name = "File"), @JsonSubTypes.Type(value = LabProcessEntity.class, name = "LabProcess"), @JsonSubTypes.Type(value = MetadataEntity.class, name = "CreativeWork"), diff --git a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/repositories/ProjectRepository.java b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/repositories/ProjectRepository.java index ffa01138..211083ad 100644 --- a/eva-lib/src/main/java/uk/ac/ebi/eva/lib/repositories/ProjectRepository.java +++ b/eva-lib/src/main/java/uk/ac/ebi/eva/lib/repositories/ProjectRepository.java @@ -21,4 +21,5 @@ public interface ProjectRepository extends JpaRepository { Project getProjectByProjectAccession(String accession); + } diff --git a/eva-server/src/main/java/uk/ac/ebi/eva/server/ws/StudyWSServer.java b/eva-server/src/main/java/uk/ac/ebi/eva/server/ws/StudyWSServer.java index 13df423a..83227dbd 100644 --- a/eva-server/src/main/java/uk/ac/ebi/eva/server/ws/StudyWSServer.java +++ b/eva-server/src/main/java/uk/ac/ebi/eva/server/ws/StudyWSServer.java @@ -125,4 +125,14 @@ public ResponseEntity getStudyRoCrate(@PathVariable("study") St } return ResponseEntity.ok(roCrateMetadata); } + + @RequestMapping(value = "/ro-crate", method = RequestMethod.GET) + public ResponseEntity getAllRoCrates() { + RoCrateMetadata roCrateMetadata = roCrateMetadataAdaptor.getAllProjects(); + if (roCrateMetadata == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + ResponseEntity response = ResponseEntity.ok(roCrateMetadata); + return response; + } } diff --git a/eva-server/src/test/java/uk/ac/ebi/eva/server/ws/StudyWSServerTest.java b/eva-server/src/test/java/uk/ac/ebi/eva/server/ws/StudyWSServerTest.java index 19257640..c10882b1 100644 --- a/eva-server/src/test/java/uk/ac/ebi/eva/server/ws/StudyWSServerTest.java +++ b/eva-server/src/test/java/uk/ac/ebi/eva/server/ws/StudyWSServerTest.java @@ -24,7 +24,9 @@ import uk.ac.ebi.eva.lib.entities.Submission; import uk.ac.ebi.eva.lib.entities.Taxonomy; import uk.ac.ebi.eva.lib.models.rocrate.CommentEntity; -import uk.ac.ebi.eva.lib.models.rocrate.DatasetEntity; +import uk.ac.ebi.eva.lib.models.rocrate.DataCatalogEntity; +import uk.ac.ebi.eva.lib.models.rocrate.MinimalProjectDatasetEntity; +import uk.ac.ebi.eva.lib.models.rocrate.ProjectDatasetEntity; import uk.ac.ebi.eva.lib.models.rocrate.FileEntity; import uk.ac.ebi.eva.lib.models.rocrate.LabProcessEntity; import uk.ac.ebi.eva.lib.models.rocrate.MetadataEntity; @@ -175,7 +177,7 @@ public void testGetRoCrate() { assertEquals("ro-crate-metadata.json", metadata.getId()); // Second entity is the dataset, corresponding to the project - DatasetEntity dataset = (DatasetEntity) roCrateMetadata.getGraph().get(1); + ProjectDatasetEntity dataset = (ProjectDatasetEntity) roCrateMetadata.getGraph().get(1); assertEquals("PRJEB0001", dataset.getProjectAccession()); assertEquals("project title", dataset.getName()); assertEquals(LocalDate.of(2025, 1, 1), dataset.getDatePublished()); @@ -183,7 +185,7 @@ public void testGetRoCrate() { // Additional properties of project are separate entities List taxonomyRefs = dataset.getAdditionalProperties() .stream() - .filter(ref -> ref.getId().equalsIgnoreCase("#taxonomyId")) + .filter(ref -> ref.getId().equalsIgnoreCase("#PRJEB0001-taxonomyId")) .collect(Collectors.toList()); List taxonomyEntities = roCrateMetadata.getEntities(taxonomyRefs); assertEquals(1, taxonomyEntities.size()); @@ -218,6 +220,31 @@ public void testGetRoCrate() { sampleEntities.stream().map(SampleEntity::getName).collect(Collectors.toSet())); } + @Test + public void testGetRoCrateCatalog() { + String url = "/v1/studies/ro-crate"; + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, null, + RoCrateMetadata.class); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + RoCrateMetadata roCrateMetadata = response.getBody(); + + // First entity describes the metadata document itself + MetadataEntity metadata = (MetadataEntity) roCrateMetadata.getGraph().get(0); + assertEquals("ro-crate-metadata.json", metadata.getId()); + + // Second entity is the dataCatalog for all of EVA's projects + DataCatalogEntity dataCatalog = (DataCatalogEntity) roCrateMetadata.getGraph().get(1); + assertEquals("EVA studies", dataCatalog.getIdentifier()); + assertEquals(LocalDate.of(2025, 1, 1), dataCatalog.getDatePublished()); + + // Check project entities (dataset) + List projects = roCrateMetadata.getEntitiesOfType("Dataset"); + assertEquals(1, projects.size()); + MinimalProjectDatasetEntity project = (MinimalProjectDatasetEntity) projects.stream().sorted().findFirst().get(); + assertEquals("PRJEB0001", project.getProjectAccession()); + } + @Test public void testGetRoCrateNotFound() { String url = "/v1/studies/ro-crate/PRJEB999";