diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index ba8a9308..fe8a5ce3 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -11,7 +11,6 @@ on: push: branches: [ main, development ] pull_request: - branches: [ main, development ] workflow_dispatch: env: diff --git a/README.md b/README.md index e5613a23..6d896722 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ and avoiding crates which do not fully comply to the specification, at the same - [Instructions for your build manager (e.g., Gradle, Maven, etc.)](https://central.sonatype.com/artifact/edu.kit.datamanager/ro-crate-java) - [Quick-Start](#quick-start) -- [Adapting Specification Examples](#adapting-the-specification-examples) +- [JavaDoc Documentation](https://javadoc.io/doc/edu.kit.datamanager/ro-crate-java) - [Related Publications](https://publikationen.bibliothek.kit.edu/publikationslisten/get.php?referencing=all&external_publications=kit&lang=de&format=html&style=kit-3lines-title_b-authors-other&consider_suborganizations=true&order=desc%20year&contributors=%5B%5B%5B%5D%2C%5B%22p20751.105%22%5D%5D%5D&title_contains=crate) ## Build the library / documentation @@ -31,687 +31,34 @@ On Windows, replace `./gradlew` with `gradlew.bat`. ## RO-Crate Specification Compatibility -- ✅ Version 1.1 +- ✅ [Version 1.1](https://www.researchobject.org/ro-crate/1.1/) ([Extracted examples as well-described unit tests/guide](src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java)) - 🛠️ Version 1.2-DRAFT - ✅ Reading and writing crates with additional profiles or specifications ([examples for reading](src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java), [examples for writing](src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java)) - ✅ Adding profiles or other specifications to a crate ([examples](src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java)) ## Quick-start -### Example for a basic crate from [RO-Crate website](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor) -```java -RoCrate roCrate = new RoCrateBuilder("name", "description", "datePublished", "licenseIdentifier").build(); -``` -### Example adding a File (Data Entity) and a context pair +` ro-crate-java` makes use of the builder pattern to guide the user to create a valid RO-Crate, similar to: + ```java -RoCrate roCrate = new RoCrateBuilder("name", "description", "datePublished", "licenseIdentifier") - .addValuePairToContext("Station", "www.station.com") - .addUrlToContext("contextUrl") +RoCrate myFirstCrate = STARTER_CRATE .addDataEntity( - new FileEntity.FileEntityBuilder() - .setId("survey-responses-2019.csv") - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .build() + new FileEntity.FileEntityBuilder() + .setId("path/within/crate/survey-responses-2019.csv") + .setLocation(Paths.get("path/to/current/location/experiment.csv")) + .addProperty("name", "Survey responses") + .addProperty("contentSize", "26452") + .addProperty("encodingFormat", "text/csv") + .build() ) - .addDataEntity(...) - ... - .addContextualEntity(...) - ... - .build(); -``` - -The library currently comes with three specialized DataEntities: - -1. `DataSetEntity` -2. `FileEntity` (used in the example above) -3. `WorkflowEntity` - -If another type of `DataEntity` is required, the base class `DataEntity` can be used. Example: -```java -new DataEntity.DataEntityBuilder() - .addType("CreativeWork") - .setId("ID") - .addProperty("property from schema.org/Creativework", "value") - .build(); -``` -Note that here you are supposed to add the type of your `DataEntity` because it is not known. - -A `DataEntity` and its subclasses can have a file located on the web. Example: - -Example adding file: -```java -new FileEntity.FileEntityBuilder() - .addContent(URI.create("https://github.com/kit-data-manager/ro-crate-java/issues/5")) - .addProperty("description", "my new file that I added") - .build(); -``` - -A `DataEntity` and its subclasses can have a local file associated with them, -instead of one located on the web (which link is the ID of the data entity). Example: - -Example adding file: -```java -new FileEntity.FileEntityBuilder() - .addContent(Paths.get("file"), "new_file.txt") - .addProperty("description", "my new local file that I added") - .build(); -``` - -### Contextual Entities - -Contextual entities cannot be associated with a file (they are pure metadata). - -To add a contextual entity to a crate you use the function `.addContextualEntity(ContextualEntity entity)`. -Some types of derived/specializes entities are: -1. `OrganizationEntity` -2. `PersonEntity` -3. `PlaceEntity` - -If you need another type of contextual entity, use the base class `ContextualEntity`. - -The library provides a way to automatically create contextual entities from external providers. Currently, support for [ORCID](https://orcid.org/) and [ROR](https://ror.org/) is implemented. Example: -```java -PersonEntity person = ORCIDProvider.getPerson("https://orcid.org/*") -OrganizationEntity organization = RORProvider.getOrganization("https://ror.org/*"); -``` - -### Writing Crate to folder, zip file, or zip stream - -Writing to folder: -```java -RoCrateWriter folderRoCrateWriter = new RoCrateWriter(new FolderWriter()); -folderRoCrateWriter.save(roCrate, "destinationFolder"); -``` - -Writing to zip file: -```java -RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); -roCrateZipWriter.save(roCrate, "destinationFolder"); -``` - -Writing to zip stream: -```java -RoCrateWriter roCrateZipStreamWriter = new RoCrateWriter(new ZipStreamWriter()); -roCrateZipStreamWriter.save(roCrate, outputStream); -``` - -More writing strategies can be implemented, if required. - -### Reading / importing Crate from folder or zip - -Reading from folder: -```java -RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); -RoCrate res = roCrateFolderReader.readCrate("destinationFolder"); -``` - -Reading from zip file: -```java -RoCrateReader roCrateFolderReader = new RoCrateReader(new ZipReader()); -RoCrate crate = roCrateFolderReader.readCrate("sourceZipFile"); -``` - -Reading from zip stream: -```java -RoCrateReader roCrateFolderReader = new RoCrateReader(new ZipStreamReader()); -RoCrate crate = roCrateFolderReader.readCrate(inputStream); -``` - -### RO-Crate Website (HTML preview file) -ro-crate-java offers tree different kinds of previews: - -* AutomaticPreview: Uses third-party library [ro-crate-html-js](https://www.npmjs.com/package/ro-crate-html-js), which must be installed separately. -* CustomPreview: Pure Java-based preview using an included template processed by the FreeMarker template engine. At the same time, CustomPreview is the fallback for AutomaticPreview if ro-crate-html-js is not installed. -* StaticPreview: Allows to provide a static HTML page (including additional dependencies, e.g., CSS, JS) which is then shipped with the RO-Crate. - -When creating a new RO-Crate using the builder, the default setting is to use CustomPreview. If you want to change this behaviour, thr preview method is set as follows: - -```java -RoCrate roCrate = new RoCrateBuilder("name", "description", "datePublished", "licenseIdentifier") - .setPreview(new AutomaticPreview()) + .addDataEntity(/*...*/) + .addContextualEntity(/*...*/) .build(); ``` -Keep in mind that, if you want to use AutomaticPreview, you have to install ro-crate-html-js via `npm install --global ro-crate-html-js` first. +A built or imported crate can of course also be modified afterwards. Take a look at our further documentation: -For StaticPreview, the constuctor is a bit different, such that it looks as follows: - -```java -File pathToMainPreviewHtml = new File("localPath"); -File pathToAdditionalFiles = new File("localFolder"); -RoCrate roCrate = new RoCrateBuilder("name", "description", "datePublished", "licenseIdentifier") - .setPreview(new StaticPreview(pathToMainPreviewHtml, pathToAdditionalFiles)) - .build(); -``` - -### RO-Crate validation (machine-readable crate profiles) -Right now, the only implemented way of validating a RO-crate is to use a [JSON-Schema](https://json-schema.org/) that the crates metadata JSON file should match. JSON-Schema is an established standard and therefore a good choice for a crate profile. Example: - -```java -Validator validator = new Validator(new JsonSchemaValidation("./schema.json")); -boolean valid = validator.validate(crate); -``` - -## Adapting the specification examples - -This section describes how to generate the [official specifications examples](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#minimal-example-of-ro-crate). Each example first shows the ro-crate-metadata.json and, below that, the required Java code to generate it. - -### [Minimal example](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#minimal-example-of-ro-crate) - -```json -{ "@context": "https://w3id.org/ro/crate/1.1/context", - "@graph": [ - - { - "@type": "CreativeWork", - "@id": "ro-crate-metadata.json", - "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, - "about": {"@id": "./"} - }, - { - "@id": "./", - "identifier": "https://doi.org/10.4225/59/59672c09f4a4b", - "@type": "Dataset", - "datePublished": "2017", - "name": "Data files associated with the manuscript:Effects of facilitated family case conferencing for ...", - "description": "Palliative care planning for nursing home residents with advanced dementia ...", - "license": {"@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/"} - }, - { - "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", - "@type": "CreativeWork", - "description": "This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/au/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.", - "identifier": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", - "name": "Attribution-NonCommercial-ShareAlike 3.0 Australia (CC BY-NC-SA 3.0 AU)" - } - ] -} -``` - -Here, everything is created manually. -For the following examples, more convenient creation methods are used. - -```java - RoCrate crate = new RoCrate(); - - ContextualEntity license = new ContextualEntity.ContextualEntityBuilder() - .addType("CreativeWork") - .setId("https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addProperty("description", "This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/au/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.") - .addProperty("identifier", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addProperty("name", "Attribution-NonCommercial-ShareAlike 3.0 Australia (CC BY-NC-SA 3.0 AU)") - .build(); - - crate.setRootDataEntity(new RootDataEntity.RootDataEntityBuilder() - .addProperty("identifier", "https://doi.org/10.4225/59/59672c09f4a4b") - .addProperty("datePublished", "2017") - .addProperty("name", "Data files associated with the manuscript:Effects of facilitated family case conferencing for ...") - .addProperty("description", "Palliative care planning for nursing home residents with advanced dementia ...") - .setLicense(license) - .build()); - - crate.setJsonDescriptor(new ContextualEntity.ContextualEntityBuilder() - .setId("ro-crate-metadata.json") - .addType("CreativeWork") - .addIdProperty("about", "./") - .addIdProperty("conformsTo", "https://w3id.org/ro/crate/1.1") - .build() - ); - crate.addContextualEntity(license); -``` - -### [Example with files](https://www.researchobject.org/ro-crate/1.1/data-entities.html#example-linking-to-a-file-and-folders) - -```json -{ "@context": "https://w3id.org/ro/crate/1.1/context", - "@graph": [ - { - "@type": "CreativeWork", - "@id": "ro-crate-metadata.json", - "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, - "about": {"@id": "./"} - }, - { - "@id": "./", - "@type": [ - "Dataset" - ], - "hasPart": [ - { - "@id": "cp7glop.ai" - }, - { - "@id": "lots_of_little_files/" - } - ] - }, - { - "@id": "cp7glop.ai", - "@type": "File", - "name": "Diagram showing trend to increase", - "contentSize": "383766", - "description": "Illustrator file for Glop Pot", - "encodingFormat": "application/pdf" - }, - { - "@id": "lots_of_little_files/", - "@type": "Dataset", - "name": "Too many files", - "description": "This directory contains many small files, that we're not going to describe in detail." - } - ] -} -``` - -Here we use the inner builder classes for the construction of the crate. -Doing so, the Metadata File Descriptor and the Root Data Entity entities are added automatically. -`setSource()` is used to provide the actual location of these Data Entities (if they are not remote). -The Data Entity file in the crate will have the name of the entity's ID. - -```java - RoCrate crate = new RoCrate.RoCrateBuilder() - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addContent (Paths.get("path to file"), "cp7glop.ai") - .addProperty("name", "Diagram showing trend to increase") - .addProperty("contentSize", "383766") - .addProperty("description", "Illustrator file for Glop Pot") - .setEncodingFormat("application/pdf") - .build() - ) - .addDataEntity( - new DataSetEntity.DataSetBuilder() - .addContent (Paths.get("path_to_files"), "lots_of_little_files/") - .addProperty("name", "Too many files") - .addProperty("description", "This directory contains many small files, that we're not going to describe in detail.") - .build() - ) - .build(); -``` - -### [Example with web resources](https://www.researchobject.org/ro-crate/1.1/data-entities.html#web-based-data-entities) - -```json -{ "@context": "https://w3id.org/ro/crate/1.1/context", - "@graph": [ - { - "@type": "CreativeWork", - "@id": "ro-crate-metadata.json", - "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, - "about": {"@id": "./"} - }, - { - "@id": "./", - "@type": [ - "Dataset" - ], - "hasPart": [ - { - "@id": "survey-responses-2019.csv" - }, - { - "@id": "https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf" - }, - ] - }, - { - "@id": "survey-responses-2019.csv", - "@type": "File", - "name": "Survey responses", - "contentSize": "26452", - "encodingFormat": "text/csv" - }, - { - "@id": "https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf", - "@type": "File", - "name": "RO-Crate specification", - "contentSize": "310691", - "description": "RO-Crate specification", - "encodingFormat": "application/pdf" - } -] -} -``` - -The web resource does not use `.setSource()`, but uses the ID to indicate the file's location. - -```java - RoCrate crate = new RoCrate.RoCrateBuilder() - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addContent (Paths.get("README.md"), "survey-responses-2019.csv") - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .setEncodingFormat("text/csv") - .build() - ) - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addContent(URI.create("https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf")) - .addProperty("name", "RO-Crate specification") - .addProperty("contentSize", "310691") - .addProperty("description", "RO-Crate specification") - .setEncodingFormat("application/pdf") - .build() - ) - .build(); -``` - -### [Example with file, author, location](https://www.researchobject.org/ro-crate/1.1/appendix/jsonld.html) - -```json -{ "@context": "https://w3id.org/ro/crate/1.1/context", - "@graph": [ - - { - "@type": "CreativeWork", - "@id": "ro-crate-metadata.json", - "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, - "about": {"@id": "./"}, - "description": "RO-Crate Metadata File Descriptor (this file)" - }, - { - "@id": "./", - "@type": "Dataset", - "name": "Example RO-Crate", - "description": "The RO-Crate Root Data Entity", - "datePublished": "2020", - "license": {"@id": "https://spdx.org/licenses/CC-BY-NC-SA-4.0"}, - "hasPart": [ - {"@id": "data1.txt"}, - {"@id": "data2.txt"} - ] - }, - { - "@id": "data1.txt", - "@type": "File", - "description": "One of hopefully many Data Entities", - "author": {"@id": "#alice"}, - "contentLocation": {"@id": "http://sws.geonames.org/8152662/"} - }, - { - "@id": "data2.txt", - "@type": "File" - }, - - { - "@id": "#alice", - "@type": "Person", - "name": "Alice", - "description": "One of hopefully many Contextual Entities" - }, - { - "@id": "http://sws.geonames.org/8152662/", - "@type": "Place", - "name": "Catalina Park" - } - ] -} -``` - -If there is no special method for including relative entities (ID properties) one can use `.addIdProperty("key","value")`. - -```java - PersonEntity alice = new PersonEntity.PersonEntityBuilder() - .setId("#alice") - .addProperty("name", "Alice") - .addProperty("description", "One of hopefully many Contextual Entities") - .build(); - PlaceEntity park = new PlaceEntity.PlaceEntityBuilder() - .addContent(URI.create("http://sws.geonames.org/8152662/")) - .addProperty("name", "Catalina Park") - .build(); - - RoCrate crate = new RoCrate.RoCrateBuilder("Example RO-Crate", "The RO-Crate Root Data Entity", "2020", "https://spdx.org/licenses/CC-BY-NC-SA-4.0") - .addContextualEntity(park) - .addContextualEntity(alice) - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addContent(Paths.get("......."), "data2.txt") - .build() - ) - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addContent(Paths.get("......."), "data1.txt") - .addProperty("description", "One of hopefully many Data Entities") - .addAuthor(alice.getId()) - .addIdProperty("contentLocation", park) - .build() - ) - .build(); - -``` -### [Example with computational workflow](https://www.researchobject.org/ro-crate/1.1/workflows.html#complete-workflow-example) - -```json -{ "@context": "https://w3id.org/ro/crate/1.1/context", - "@graph": [ - { - "@type": "CreativeWork", - "@id": "ro-crate-metadata.json", - "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, - "about": {"@id": "./"} - }, - { - "@id": "./", - "@type": "Dataset", - "name": "Example RO-Crate", - "description": "The RO-Crate Root Data Entity", - "datePublished": "2020", - "license": {"@id": "https://spdx.org/licenses/CC-BY-NC-SA-4.0"}, - "hasPart": [ - { "@id": "workflow/alignment.knime" } - ] - }, - { - "@id": "workflow/alignment.knime", - "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], - "conformsTo": - {"@id": "https://bioschemas.org/profiles/ComputationalWorkflow/0.5-DRAFT-2020_07_21/"}, - "name": "Sequence alignment workflow", - "programmingLanguage": {"@id": "#knime"}, - "creator": {"@id": "#alice"}, - "dateCreated": "2020-05-23", - "license": { "@id": "https://spdx.org/licenses/CC-BY-NC-SA-4.0"}, - "input": [ - { "@id": "#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b"} - ], - "output": [ - { "@id": "#6c703fee-6af7-4fdb-a57d-9e8bc4486044"}, - { "@id": "#2f32b861-e43c-401f-8c42-04fd84273bdf"} - ], - "sdPublisher": {"@id": "#workflow-hub"}, - "url": "http://example.com/workflows/alignment", - "version": "0.5.0" - }, - { - "@id": "#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b", - "@type": "FormalParameter", - "conformsTo": {"@id": "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/"}, - "name": "genome_sequence", - "valueRequired": true, - "additionalType": {"@id": "http://edamontology.org/data_2977"}, - "format": {"@id": "http://edamontology.org/format_1929"} - }, - { - "@id": "#6c703fee-6af7-4fdb-a57d-9e8bc4486044", - "@type": "FormalParameter", - "conformsTo": {"@id": "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/"}, - "name": "cleaned_sequence", - "additionalType": {"@id": "http://edamontology.org/data_2977"}, - "encodingFormat": {"@id": "http://edamontology.org/format_2572"} - }, - { - "@id": "#2f32b861-e43c-401f-8c42-04fd84273bdf", - "@type": "FormalParameter", - "conformsTo": {"@id": "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/"}, - "name": "sequence_alignment", - "additionalType": {"@id": "http://edamontology.org/data_1383"}, - "encodingFormat": {"@id": "http://edamontology.org/format_1982"} - }, - { - "@id": "https://spdx.org/licenses/CC-BY-NC-SA-4.0", - "@type": "CreativeWork", - "name": "Creative Commons Attribution Non Commercial Share Alike 4.0 International", - "alternateName": "CC-BY-NC-SA-4.0" - }, - { - "@id": "#knime", - "@type": "ProgrammingLanguage", - "name": "KNIME Analytics Platform", - "alternateName": "KNIME", - "url": "https://www.knime.com/whats-new-in-knime-41", - "version": "4.1.3" - }, - { - "@id": "#alice", - "@type": "Person", - "name": "Alice Brown" - }, - { - "@id": "#workflow-hub", - "@type": "Organization", - "name": "Example Workflow Hub", - "url":"http://example.com/workflows/" - }, - { - "@id": "http://edamontology.org/format_1929", - "@type": "Thing", - "name": "FASTA sequence format" - }, - { - "@id": "http://edamontology.org/format_1982", - "@type": "Thing", - "name": "ClustalW alignment format" - }, - { - "@id": "http://edamontology.org/format_2572", - "@type": "Thing", - "name": "BAM format" - }, - { - "@id": "http://edamontology.org/data_2977", - "@type": "Thing", - "name": "Nucleic acid sequence" - }, - { - "@id": "http://edamontology.org/data_1383", - "@type": "Thing", - "name": "Nucleic acid sequence alignment" - } - ] -} -``` - - -```java - ContextualEntity license = new ContextualEntity.ContextualEntityBuilder() - .addType("CreativeWork") - .setId("https://spdx.org/licenses/CC-BY-NC-SA-4.0") - .addProperty("name", "Creative Commons Attribution Non Commercial Share Alike 4.0 International") - .addProperty("alternateName", "CC-BY-NC-SA-4.0") - .build(); - ContextualEntity knime = new ContextualEntity.ContextualEntityBuilder() - .setId("#knime") - .addType("ProgrammingLanguage") - .addProperty("name", "KNIME Analytics Platform") - .addProperty("alternateName", "KNIME") - .addProperty("url", "https://www.knime.com/whats-new-in-knime-41") - .addProperty("version", "4.1.3") - .build(); - OrganizationEntity workflowHub = new OrganizationEntity.OrganizationEntityBuilder() - .setId("#workflow-hub") - .addProperty("name", "Example Workflow Hub") - .addProperty("url", "http://example.com/workflows/") - .build(); - ContextualEntity fasta = new ContextualEntity.ContextualEntityBuilder() - .setId("http://edamontology.org/format_1929") - .addType("Thing") - .addProperty("name", "FASTA sequence format") - .build(); - ContextualEntity clustalW = new ContextualEntity.ContextualEntityBuilder() - .setId("http://edamontology.org/format_1982") - .addType("Thing") - .addProperty("name", "ClustalW alignment format") - .build(); - ContextualEntity ban = new ContextualEntity.ContextualEntityBuilder() - .setId("http://edamontology.org/format_2572") - .addType("Thing") - .addProperty("name", "BAM format") - .build(); - ContextualEntity nucSec = new ContextualEntity.ContextualEntityBuilder() - .setId("http://edamontology.org/data_2977") - .addType("Thing") - .addProperty("name", "Nucleic acid sequence") - .build(); - ContextualEntity nucAlign = new ContextualEntity.ContextualEntityBuilder() - .setId("http://edamontology.org/data_1383") - .addType("Thing") - .addProperty("name", "Nucleic acid sequence alignment") - .build(); - PersonEntity alice = new PersonEntity.PersonEntityBuilder() - .setId("#alice") - .addProperty("name", "Alice Brown") - .build(); - ContextualEntity requiredParam = new ContextualEntity.ContextualEntityBuilder() - .addType("FormalParameter") - .setId("#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b") - .addProperty("name", "genome_sequence") - .addProperty("valueRequired", true) - .addIdProperty("conformsTo", "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/") - .addIdProperty("additionalType", nucSec) - .addIdProperty("encodingFormat", fasta) - .build(); - ContextualEntity clnParam = new ContextualEntity.ContextualEntityBuilder() - .addType("FormalParameter") - .setId("#6c703fee-6af7-4fdb-a57d-9e8bc4486044") - .addProperty("name", "cleaned_sequence") - .addIdProperty("conformsTo", "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/") - .addIdProperty("additionalType", nucSec) - .addIdProperty("encodingFormat", ban) - .build(); - ContextualEntity alignParam = new ContextualEntity.ContextualEntityBuilder() - .addType("FormalParameter") - .setId("#2f32b861-e43c-401f-8c42-04fd84273bdf") - .addProperty("name", "sequence_alignment") - .addIdProperty("conformsTo", "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/") - .addIdProperty("additionalType", nucAlign) - .addIdProperty("encodingFormat", clustalW) - .build(); - - RoCrate crate = new RoCrate.RoCrateBuilder("Example RO-Crate", "The RO-Crate Root Data Entity", "2020", "https://spdx.org/licenses/CC-BY-NC-SA-4.0") - .addContextualEntity(license) - .addContextualEntity(knime) - .addContextualEntity(workflowHub) - .addContextualEntity(fasta) - .addContextualEntity(clustalW) - .addContextualEntity(ban) - .addContextualEntity(nucSec) - .addContextualEntity(nucAlign) - .addContextualEntity(alice) - .addContextualEntity(requiredParam) - .addContextualEntity(clnParam) - .addContextualEntity(alignParam) - .addDataEntity( - new WorkflowEntity.WorkflowEntityBuilder() - .setId("workflow/alignment.knime") - .setSource(new File("src")) - .addIdProperty("conformsTo", "https://bioschemas.org/profiles/ComputationalWorkflow/0.5-DRAFT-2020_07_21/") - .addProperty("name", "Sequence alignment workflow") - .addIdProperty("programmingLanguage", "#knime") - .addAuthor("#alice") - .addProperty("dateCreated", "2020-05-23") - .setLicense("https://spdx.org/licenses/CC-BY-NC-SA-4.0") - .addInput("#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b") - .addOutput("#6c703fee-6af7-4fdb-a57d-9e8bc4486044") - .addOutput("#2f32b861-e43c-401f-8c42-04fd84273bdf") - .addProperty("url", "http://example.com/workflows/alignment") - .addProperty("version", "0.5.0") - .addIdProperty("sdPublisher", "#workflow-hub") - .build() - - ) - .build(); -``` +- **There is a well-documented example-driven guide in [LearnByExampleTest.java](src/test/java/edu/kit/datamanager/ro_crate/examples/LearnByExampleTest.java) to help you get started.** +- You may also be interested in the examples we extracted from the [specification in version 1.1](https://www.researchobject.org/ro-crate/1.1/), which are available in [ExamplesOfSpecificationV1p1Test.java](src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java). +- There is a [module with all well-described guiding tests](src/test/java/edu/kit/datamanager/ro_crate/examples/) available. +- The [JavaDoc Documentation](https://javadoc.io/doc/edu.kit.datamanager/ro-crate-java) is also available online. diff --git a/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java b/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java index 330135b7..356b0159 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java @@ -10,13 +10,10 @@ import edu.kit.datamanager.ro_crate.entities.AbstractEntity; import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity; import edu.kit.datamanager.ro_crate.entities.contextual.JsonDescriptor; -import edu.kit.datamanager.ro_crate.entities.contextual.OrganizationEntity; import edu.kit.datamanager.ro_crate.entities.data.DataEntity; -import edu.kit.datamanager.ro_crate.entities.data.DataEntity.DataEntityBuilder; -import edu.kit.datamanager.ro_crate.entities.data.FileEntity; + import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity; import edu.kit.datamanager.ro_crate.externalproviders.dataentities.ImportFromDataCite; -import edu.kit.datamanager.ro_crate.externalproviders.organizationprovider.RorProvider; import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; import edu.kit.datamanager.ro_crate.payload.CratePayload; import edu.kit.datamanager.ro_crate.payload.RoCratePayload; @@ -26,12 +23,9 @@ import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions; import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation; import edu.kit.datamanager.ro_crate.validation.Validator; -import edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; import java.io.File; import java.net.URI; -import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -354,6 +348,18 @@ public RoCrateBuilder addName(String name) { return this; } + /** + * Adds an "identifier" property to the root data entity. + *

+ * This is useful e.g. to assign e.g. a DOI to this crate. + * @param identifier the identifier to add. + * @return this builder. + */ + public RoCrateBuilder addIdentifier(String identifier) { + this.rootDataEntity.addProperty("identifier", identifier.strip()); + return this; + } + public RoCrateBuilder addDescription(String description) { this.rootDataEntity.addProperty(PROPERTY_DESCRIPTION, description); return this; diff --git a/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java b/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java index 580e625b..8699fd5b 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java @@ -239,33 +239,60 @@ private static boolean addProperty(ObjectNode whereToAdd, String key, JsonNode v * @param id the "id" of the property. */ public void addIdProperty(String name, String id) { - JsonNode jsonNode = addToIdProperty(name, id, this.properties.get(name)); - if (jsonNode != null) { - this.linkedTo.add(id); - this.properties.set(name, jsonNode); - this.notifyObservers(); - } + mergeIdIntoValue(id, this.properties.get(name)) + .ifPresent(newValue -> { + this.linkedTo.add(id); + this.properties.set(name, newValue); + this.notifyObservers(); + }); } - private static JsonNode addToIdProperty(String name, String id, JsonNode property) { - ObjectMapper objectMapper = MyObjectMapper.getMapper(); - if (name != null && id != null) { - if (property == null) { - return objectMapper.createObjectNode().put("@id", id); - } else { - if (property.isArray()) { - ArrayNode ns = (ArrayNode) property; - ns.add(objectMapper.createObjectNode().put("@id", id)); - return ns; - } else { - ArrayNode newNodes = objectMapper.createArrayNode(); - newNodes.add(property); - newNodes.add(objectMapper.createObjectNode().put("@id", id)); - return newNodes; - } - } + /** + * Merges the given id into the current value, + * using this representation: {"@id" : "id"}. + *

+ * The current value can be null without errors. + * Only the id will be considered in this case. + *

+ * If the id is null-ish, it will not be added, similar to a null-ish value. + * If the id is already present, nothing will be done. + * If it is not an array and the id is not present, an array will be applied. + * + * @param id the id to add. + * @param currentValue the current value of the property. + * @return The updated value of the property. + * Empty if value does not change! + */ + private static Optional mergeIdIntoValue(String id, JsonNode currentValue) { + if (id == null || id.isBlank()) { return Optional.empty(); } + + ObjectMapper jsonBuilder = MyObjectMapper.getMapper(); + ObjectNode newIdObject = jsonBuilder.createObjectNode().put("@id", id); + if (currentValue == null || currentValue.isNull() || currentValue.isMissingNode()) { + return Optional.ofNullable(newIdObject); + } + + boolean isIdAlready = currentValue.asText().equals(id); + boolean isIdObjectAlready = currentValue.path("@id").asText().equals(id); + boolean isArrayWithIdPresent = currentValue.valueStream() + .anyMatch(node -> node + .path("@id") + .asText() + .equals(id)); + if (isIdAlready || isIdObjectAlready || isArrayWithIdPresent) { + return Optional.empty(); + } + + if (currentValue.isArray() && currentValue instanceof ArrayNode currentValueAsArray) { + currentValueAsArray.add(newIdObject); + return Optional.of(currentValueAsArray); + } else { + // property is not an array, so we make it an array + ArrayNode newNodes = jsonBuilder.createArrayNode(); + newNodes.add(currentValue); + newNodes.add(newIdObject); + return Optional.of(newNodes); } - return null; } /** @@ -369,6 +396,11 @@ protected String getId() { /** * Setting the id property of the entity, if the given value is not * null. If the id is not encoded, the encoding will be done. + *

+ * NOTE: IDs are not just names! The ID may have effects + * on parts of your crate! For example: If the entity represents a + * file which will be copied into the crate, writers must use the + * ID as filename. * * @param id the String representing the id. * @return the generic builder. @@ -486,11 +518,11 @@ public T addProperty(String key, boolean value) { * @return the generic builder */ public T addIdProperty(String name, String id) { - JsonNode jsonNode = AbstractEntity.addToIdProperty(name, id, this.properties.get(name)); - if (jsonNode != null) { - this.properties.set(name, jsonNode); - this.relatedItems.add(id); - } + AbstractEntity.mergeIdIntoValue(id, this.properties.get(name)) + .ifPresent(newValue -> { + this.properties.set(name, newValue); + this.relatedItems.add(id); + }); return self(); } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/preview/StaticPreview.java b/src/main/java/edu/kit/datamanager/ro_crate/preview/StaticPreview.java index 398615c1..5a627e17 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/preview/StaticPreview.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/preview/StaticPreview.java @@ -3,7 +3,6 @@ import edu.kit.datamanager.ro_crate.util.ZipUtil; import java.io.File; import java.io.IOException; -import java.util.Optional; import net.lingala.zip4j.ZipFile; import net.lingala.zip4j.io.outputstream.ZipOutputStream; @@ -13,7 +12,7 @@ /** * This class adds a static preview to the crate, which consists of a * metadataHtml file and a folder containing other files required to render - * metadataHtml. If will be put unchanged to the writer output, i.e., a zip + * metadataHtml. It will be put unchanged to the writer output, i.e., a zip * file, folder, or stream. * * @author jejkal diff --git a/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java b/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java index 8f32213f..2c733d3e 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import edu.kit.datamanager.ro_crate.entities.AbstractEntity; import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions; @@ -11,6 +12,7 @@ import org.apache.commons.io.FileUtils; import io.json.compare.JSONCompare; import io.json.compare.JsonComparator; +import org.opentest4j.AssertionFailedError; import java.io.File; import java.io.IOException; @@ -34,8 +36,7 @@ public static void compareEntityWithFile(AbstractEntity entity, String string) t public static void compare(JsonNode node1, JsonNode node2, Boolean equals) { var comparator = new JsonComparator() { - public boolean compareValues(Object expected, Object actual) { - + public boolean compareValues(Object expected, Object actual) { return expected.equals(actual); } @@ -43,6 +44,7 @@ public boolean compareFields(String expected, String actual) { return expected.equals(actual); } }; + if (equals) { JSONCompare.assertMatches(node1, node2, comparator); } else { @@ -73,6 +75,32 @@ public static void compareTwoCrateJson(Crate crate1, Crate crate2) throws JsonPr compare(node1, node2, true); } + public static void prettyPrintJsonString(String minimalJsonMetadata) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(minimalJsonMetadata); + // Enable pretty printing + String prettyJson = objectMapper + .enable(SerializationFeature.INDENT_OUTPUT) + .writeValueAsString(jsonNode); + // Print the pretty JSON + System.out.println(prettyJson); + } catch (JsonProcessingException e) { + throw new AssertionFailedError("Not able to process string as JSON!", e); + } + } + + public static void printAndAssertEquals(RoCrate crate, String pathToResource) { + // So you get something to see + prettyPrintJsonString(crate.getJsonMetadata()); + // Compare with the example from the specification + try { + HelpFunctions.compareCrateJsonToFileInResources(crate, pathToResource); + } catch (IOException e) { + throw new AssertionFailedError("Missing resources file!", e); + } + } + public static void compareCrateJsonToFileInResources(File file1, File file2) throws IOException { ObjectMapper objectMapper = MyObjectMapper.getMapper(); JsonNode node1 = JsonUtilFunctions.unwrapSingleArray(objectMapper.readTree(file1)); @@ -80,6 +108,13 @@ public static void compareCrateJsonToFileInResources(File file1, File file2) thr compare(node1, node2, true); } + /** + * Compares the JSON metadata of a Crate object with a JSON file in the resources directory. + * + * @param crate1 The Crate object to compare. + * @param jsonFileString The path to the JSON file in the resources directory. + * @throws IOException If an error occurs while reading the JSON file. + */ public static void compareCrateJsonToFileInResources(Crate crate1, String jsonFileString) throws IOException { InputStream inputStream = HelpFunctions.class.getResourceAsStream( jsonFileString); diff --git a/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java b/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java new file mode 100644 index 00000000..d699eeae --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java @@ -0,0 +1,407 @@ +package edu.kit.datamanager.ro_crate.examples; + +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.entities.AbstractEntity; +import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity; +import edu.kit.datamanager.ro_crate.entities.contextual.OrganizationEntity; +import edu.kit.datamanager.ro_crate.entities.contextual.PersonEntity; +import edu.kit.datamanager.ro_crate.entities.contextual.PlaceEntity; +import edu.kit.datamanager.ro_crate.entities.data.*; + +import edu.kit.datamanager.ro_crate.writer.CrateWriter; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.nio.file.Paths; + +import static edu.kit.datamanager.ro_crate.HelpFunctions.printAndAssertEquals; + +/** + * This class contains examples of the RO-Crate specification version 1.1. + *

+ * This is supposed to serve both as a user guide and as a test for the implementation. + * Executing a test may also print some interesting information to the console. + */ +public class ExamplesOfSpecificationV1p1Test { + + /** + * From: + * Minimal Example + * (location in repo) + *

+ * This example produces a minimal crate with a + * name, description, date, license and identifier. + *

+ * This example produces the same result as + * {@link #testMinimalCrateWithoutCrateBuilder()}, but using more convenient APIs. + */ + @Test + void testMinimalCrateConvenient() { + String licenseID = "https://creativecommons.org/licenses/by-nc-sa/3.0/au/"; + RoCrate minimal = new RoCrate.RoCrateBuilder( + "Data files associated with the manuscript:Effects of facilitated family case conferencing for ...", + "Palliative care planning for nursing home residents with advanced dementia ...", + "2017", + licenseID + ) + // We already had to set the license ID in the builder, + // but we can override it with more details to fit the example: + .setLicense( new ContextualEntity.ContextualEntityBuilder() + .addType("CreativeWork") + .setId(licenseID) + .addProperty("description", "This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/au/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.") + .addProperty("identifier", licenseID) + .addProperty("name", "Attribution-NonCommercial-ShareAlike 3.0 Australia (CC BY-NC-SA 3.0 AU)") + .build() + ) + .addIdentifier("https://doi.org/10.4225/59/59672c09f4a4b") + .build(); + + printAndAssertEquals(minimal, "/spec-v1.1-example-json-files/minimal.json"); + } + + /** + * From: + * Minimal Example + * (location in repo) + *

+ * In this example, the minimal crate is created without the builder. + * This should only be done if necessary: Use the builder if possible. + * This example produces the same result as {@link #testMinimalCrateConvenient()}. + */ + @Test + void testMinimalCrateWithoutCrateBuilder() { + RoCrate minimal = new RoCrate(); + + ContextualEntity license = new ContextualEntity.ContextualEntityBuilder() + .addType("CreativeWork") + .setId("https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .addProperty("description", "This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/au/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.") + .addProperty("identifier", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .addProperty("name", "Attribution-NonCommercial-ShareAlike 3.0 Australia (CC BY-NC-SA 3.0 AU)") + .build(); + + minimal.setRootDataEntity(new RootDataEntity.RootDataEntityBuilder() + .addProperty("identifier", "https://doi.org/10.4225/59/59672c09f4a4b") + .addProperty("datePublished", "2017") + .addProperty("name", "Data files associated with the manuscript:Effects of facilitated family case conferencing for ...") + .addProperty("description", "Palliative care planning for nursing home residents with advanced dementia ...") + .setLicense(license) + .build()); + + // This is pretty low-level. We are considering hiding/replacing this detailed API in major versions, + // so tell us (for example, open an issue) if you have a use case for it! + minimal.setJsonDescriptor(new ContextualEntity.ContextualEntityBuilder() + .setId("ro-crate-metadata.json") + .addType("CreativeWork") + .addIdProperty("about", "./") + .addIdProperty("conformsTo", "https://w3id.org/ro/crate/1.1") + .build() + ); + minimal.addContextualEntity(license); + + printAndAssertEquals(minimal, "/spec-v1.1-example-json-files/minimal.json"); + } + + // https://www.researchobject.org/ro-crate/specification/1.1/data-entities.html#example-linking-to-a-file-and-folders + + /** + * From: + * "Example linking to a file and folders" + * (location in repo) + *

+ * This example adds a File(Entity) and a DataSet(Entity) to the crate. + * The file and the folder are referenced by their location. This way + * they will be copied to the crate when writing it using a + * {@link CrateWriter}. + * The name of the file and the folder will be implicitly set to the + * ID of the respective entity in order to conform to the specification. + *

+ * Here we use the inner builder classes for the construction of the + * crate. In contrast to {@link #testMinimalCrateWithoutCrateBuilder()}, + * we do not have to care about specification details. + */ + @Test + void testLinkingToFileAndFolders() { + RoCrate crate = new RoCrate.RoCrateBuilder() + .addDataEntity( + new FileEntity.FileEntityBuilder() + // This will tell us where the file is located. It will be copied to the crate. + .setLocation(Paths.get("path to file")) + // If no ID is given explicitly, the ID will be set to the filename. + // Changing the ID means also to set the file name within the crate! + .setId("cp7glop.ai") + .addProperty("name", "Diagram showing trend to increase") + .addProperty("contentSize", "383766") + .addProperty("description", "Illustrator file for Glop Pot") + .setEncodingFormat("application/pdf") + .build() + ) + .addDataEntity( + new DataSetEntity.DataSetBuilder() + .setLocation(Paths.get("path_to_files")) + .setId("lots_of_little_files/") + .addProperty("name", "Too many files") + .addProperty("description", "This directory contains many small files, that we're not going to describe in detail.") + .build() + ) + .build(); + + printAndAssertEquals(crate, "/spec-v1.1-example-json-files/files-and-folders.json"); + } + + /** + * From: + * Example with web-based data entities + * (location in repo) + *

+ * This example adds twp FileEntities to the crate. + * One is a local file, the other one is located in the web + * and will not be copied to the crate. + */ + @Test + void testWebBasedDataEntities() { + RoCrate crate = new RoCrate.RoCrateBuilder() + .addDataEntity( + new FileEntity.FileEntityBuilder() + .setLocation(Paths.get("README.md")) + .setId("survey-responses-2019.csv") + .addProperty("name", "Survey responses") + .addProperty("contentSize", "26452") + .setEncodingFormat("text/csv") + .build() + ) + .addDataEntity( + new FileEntity.FileEntityBuilder() + .setLocation(URI.create("https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf")) + .addProperty("name", "RO-Crate specification") + .addProperty("contentSize", "310691") + .addProperty("description", "RO-Crate specification") + .setEncodingFormat("application/pdf") + .build() + ) + .build(); + + printAndAssertEquals(crate, "/spec-v1.1-example-json-files/web-based-data-entities.json"); + } + + /** + * From: + * Example with file, author, and location + * (location in repo) + *

+ * This example shows how to connect entities. If there is no specific method like + * {@link DataEntity.DataEntityBuilder#addAuthor(String)} for referencing other + * entities, one can use the more generic + * {@link AbstractEntity.AbstractEntityBuilder#addIdProperty(String, AbstractEntity)} + * or {@link AbstractEntity.AbstractEntityBuilder#addIdProperty(String, String)}. + *

+ * Important Note! If you connect entities, make sure all entities are being + * added to the crate. We currently can't enforce this properly yet. + */ + @Test + void testWithFileAuthorLocation() { + // These two entities will be connected to others later on. Therefore, we make + // them easier referencable. Referencing can be done using the whole entity or + // its ID. + final PersonEntity alice = new PersonEntity.PersonEntityBuilder() + .setId("#alice") + .addProperty("name", "Alice") + .addProperty("description", "One of hopefully many Contextual Entities") + .build(); + final PlaceEntity park = new PlaceEntity.PlaceEntityBuilder() + .setId(URI.create("http://sws.geonames.org/8152662/").toString()) + .addProperty("name", "Catalina Park") + .build(); + final String licenseId = "https://spdx.org/licenses/CC-BY-NC-SA-4.0"; + + final RoCrate crate = new RoCrate.RoCrateBuilder( + "Example RO-Crate", + "The RO-Crate Root Data Entity", + "2020", + licenseId + ) + .addContextualEntity(park) + .addContextualEntity(alice) + .addDataEntity( + new FileEntity.FileEntityBuilder() + .setLocation(Paths.get(".......")) + .setId("data2.txt") + .build() + ) + .addDataEntity( + new FileEntity.FileEntityBuilder() + .setLocation(Paths.get(".......")) + .setId("data1.txt") + .addProperty("description", "One of hopefully many Data Entities") + // ↓ This is the specific way to add an author + .addAuthor(alice.getId()) + // ↓ This is the generic way to add a location or other relations + .addIdProperty("contentLocation", park) + .build() + ) + .build(); + + /* + The builder enforces to provide a license and a publishing date, + but the example does not have them. So we have to remove them below: + */ + + // **Note**: When you add a license, even if only by a string, the crate will + // implicitly also get a small ContextEntity for this license. When we remove + // this (any) entity, all references to it will be removed as well to ensure + // consistency within the crate. Therefore, there will be no trace left of + // the license. + crate.deleteEntityById(licenseId); + + // The datePublished property is a simple property and simple to remove without + // any further internal checks. + crate.getRootDataEntity().removeProperty("datePublished"); + + printAndAssertEquals(crate, "/spec-v1.1-example-json-files/file-author-location.json"); + } + + /** + * From: + * Example with complete workflow + * (location in repo) + *

+ * This example shows how to connect entities. If there is no specific method like + * {@link DataEntity.DataEntityBuilder#addAuthor(String)} for referencing other + * entities, one can use the more generic + * {@link AbstractEntity.AbstractEntityBuilder#addIdProperty(String, AbstractEntity)} + * or {@link AbstractEntity.AbstractEntityBuilder#addIdProperty(String, String)}. + *

+ * Important Note! If you connect entities, make sure all entities are being + * added to the crate. We currently can't enforce this properly yet. + */ + @Test + void testCompleteWorkflowExample() { + final String licenseId = "https://spdx.org/licenses/CC-BY-NC-SA-4.0"; + ContextualEntity license = new ContextualEntity.ContextualEntityBuilder() + .addType("CreativeWork") + .setId(licenseId) + .addProperty("name", "Creative Commons Attribution Non Commercial Share Alike 4.0 International") + .addProperty("alternateName", "CC-BY-NC-SA-4.0") + .build(); + ContextualEntity knime = new ContextualEntity.ContextualEntityBuilder() + .setId("#knime") + .addType("ComputerLanguage") + .addProperty("name", "KNIME Analytics Platform") + .addProperty("alternateName", "KNIME") + .addProperty("url", "https://www.knime.com/whats-new-in-knime-41") + .addProperty("version", "4.1.3") + .build(); + OrganizationEntity workflowHub = new OrganizationEntity.OrganizationEntityBuilder() + .setId("#workflow-hub") + .addProperty("name", "Example Workflow Hub") + .addProperty("url", "http://example.com/workflows/") + .build(); + ContextualEntity fasta = new ContextualEntity.ContextualEntityBuilder() + .setId("http://edamontology.org/format_1929") + .addType("Thing") + .addProperty("name", "FASTA sequence format") + .build(); + ContextualEntity clustalW = new ContextualEntity.ContextualEntityBuilder() + .setId("http://edamontology.org/format_1982") + .addType("Thing") + .addProperty("name", "ClustalW alignment format") + .build(); + ContextualEntity ban = new ContextualEntity.ContextualEntityBuilder() + .setId("http://edamontology.org/format_2572") + .addType("Thing") + .addProperty("name", "BAM format") + .build(); + ContextualEntity nucSec = new ContextualEntity.ContextualEntityBuilder() + .setId("http://edamontology.org/data_2977") + .addType("Thing") + .addProperty("name", "Nucleic acid sequence") + .build(); + ContextualEntity nucAlign = new ContextualEntity.ContextualEntityBuilder() + .setId("http://edamontology.org/data_1383") + .addType("Thing") + .addProperty("name", "Nucleic acid sequence alignment") + .build(); + PersonEntity alice = new PersonEntity.PersonEntityBuilder() + .setId("#alice") + .addProperty("name", "Alice Brown") + .build(); + ContextualEntity requiredParam = new ContextualEntity.ContextualEntityBuilder() + .addType("FormalParameter") + .setId("#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b") + .addProperty("name", "genome_sequence") + .addProperty("valueRequired", true) + .addIdProperty("conformsTo", "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/") + .addIdProperty("additionalType", nucSec) + .addIdProperty("format", fasta) + .build(); + ContextualEntity clnParam = new ContextualEntity.ContextualEntityBuilder() + .addType("FormalParameter") + .setId("#6c703fee-6af7-4fdb-a57d-9e8bc4486044") + .addProperty("name", "cleaned_sequence") + .addIdProperty("conformsTo", "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/") + .addIdProperty("additionalType", nucSec) + .addIdProperty("encodingFormat", ban) + .build(); + ContextualEntity alignParam = new ContextualEntity.ContextualEntityBuilder() + .addType("FormalParameter") + .setId("#2f32b861-e43c-401f-8c42-04fd84273bdf") + .addProperty("name", "sequence_alignment") + .addIdProperty("conformsTo", "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/") + .addIdProperty("additionalType", nucAlign) + .addIdProperty("encodingFormat", clustalW) + .build(); + + RoCrate crate = new RoCrate.RoCrateBuilder( + "Example RO-Crate", + "The RO-Crate Root Data Entity", + "2020", + licenseId + ) + .setLicense(license) + .addContextualEntity(knime) + .addContextualEntity(workflowHub) + .addContextualEntity(fasta) + .addContextualEntity(clustalW) + .addContextualEntity(ban) + .addContextualEntity(nucSec) + .addContextualEntity(nucAlign) + .addContextualEntity(alice) + .addContextualEntity(requiredParam) + .addContextualEntity(clnParam) + .addContextualEntity(alignParam) + .addDataEntity( + new WorkflowEntity.WorkflowEntityBuilder() + .setId("workflow/alignment.knime") + .setLocation(Paths.get("src")) + .addIdProperty("conformsTo", "https://bioschemas.org/profiles/ComputationalWorkflow/0.5-DRAFT-2020_07_21/") + .addProperty("name", "Sequence alignment workflow") + .addIdProperty("programmingLanguage", "#knime") + // This example does not use the term "author"... + //.addAuthor("#alice") + // instead, it uses "creator": + .addIdProperty("creator", "#alice") + .addProperty("dateCreated", "2020-05-23") + .setLicense(licenseId) + .addInput("#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b") + .addOutput("#6c703fee-6af7-4fdb-a57d-9e8bc4486044") + .addOutput("#2f32b861-e43c-401f-8c42-04fd84273bdf") + .addProperty("url", "http://example.com/workflows/alignment") + .addProperty("version", "0.5.0") + .addIdProperty("sdPublisher", "#workflow-hub") + .build() + ) + .build(); + + // Similar to the previous example, this example from the specification + // spared out some details we now need to remove. + // Here we do not want to remove the license, only the reference to our root data entity. + // This is because (the way we constructed the crate) other entities use the license as well. + crate.getRootDataEntity().removeProperty("license"); + crate.getRootDataEntity().removeProperty("datePublished"); + crate.getRootDataEntity().removeProperty("name"); + crate.getRootDataEntity().removeProperty("description"); + + printAndAssertEquals(crate, "/spec-v1.1-example-json-files/complete-workflow-example.json"); + } +} diff --git a/src/test/java/edu/kit/datamanager/ro_crate/examples/LearnByExampleTest.java b/src/test/java/edu/kit/datamanager/ro_crate/examples/LearnByExampleTest.java new file mode 100644 index 00000000..aab61632 --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/examples/LearnByExampleTest.java @@ -0,0 +1,417 @@ +package edu.kit.datamanager.ro_crate.examples; + +import edu.kit.datamanager.ro_crate.HelpFunctions; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity; +import edu.kit.datamanager.ro_crate.entities.contextual.OrganizationEntity; +import edu.kit.datamanager.ro_crate.entities.contextual.PersonEntity; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; +import edu.kit.datamanager.ro_crate.entities.data.FileEntity; +import edu.kit.datamanager.ro_crate.externalproviders.organizationprovider.RorProvider; +import edu.kit.datamanager.ro_crate.externalproviders.personprovider.OrcidProvider; +import edu.kit.datamanager.ro_crate.preview.AutomaticPreview; +import edu.kit.datamanager.ro_crate.preview.StaticPreview; +import edu.kit.datamanager.ro_crate.reader.CrateReader; +import edu.kit.datamanager.ro_crate.reader.GenericReaderStrategy; +import edu.kit.datamanager.ro_crate.reader.Readers; +import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation; +import edu.kit.datamanager.ro_crate.validation.Validator; +import edu.kit.datamanager.ro_crate.writer.CrateWriter; +import edu.kit.datamanager.ro_crate.writer.FolderStrategy; +import edu.kit.datamanager.ro_crate.writer.GenericWriterStrategy; +import edu.kit.datamanager.ro_crate.writer.Writers; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.*; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * This class is meant to be a small example-driven introduction to the ro-crate-java library. + * It is meant to be read from top to bottom. + */ +public class LearnByExampleTest { + + /** + * This creates a valid, empty RO-Crate builder. + */ + static final RoCrate.RoCrateBuilder STARTER_CRATE = new RoCrate.RoCrateBuilder( + "name", + "description", + "2025", + "licenseIdentifier" + ); + + /** + * Calling the `build()` method on the builder creates a valid RO-Crate. + * Run this test to view the STARTER_CRATE JSON in the console. + */ + @Test + void aSimpleCrate() { + RoCrate almostEmptyCrate = STARTER_CRATE.build(); + assertNotNull(almostEmptyCrate); + HelpFunctions.prettyPrintJsonString(almostEmptyCrate.getJsonMetadata()); + } + + /** + * This is how we can add things to a crate. + *

+ * Note that methods starting with `add` can be used multiple times to add more. + * For example, we can add multiple files or multiple contexts. + *

+ * On the other hand, methods starting with `set` will override previous calls. + *

+ * There may be inconsistencies yet, which are tracked here: Issue #242 + */ + @Test + void addingYourFirstEntity() { + RoCrate myFirstCrate = STARTER_CRATE + // We can add new terms to our crate. The terms we can use are called "context". + .addValuePairToContext("Station", "www.station.com") + // We can also add whole contexts to our crate. + .addUrlToContext("contextUrl") + // Let's add a file to our crate. + .addDataEntity( + new FileEntity.FileEntityBuilder() + // For files (or folders, which are DataSetEntities), + // the ID determines the file name in the crate. + .setId("survey-responses-2019.csv") + // This is where we get the file from. The path will not be part of the metadata. + .setLocation(Paths.get("copy/from/this/file-and-rename-it.csv")) + // And now, the remaining metadata. + // Note that "name", "contentSize", and "encodingFormat" + // are already defined in our default context. + .addProperty("name", "Survey responses") + .addProperty("contentSize", "26452") + .addProperty("encodingFormat", "text/csv") + .build() + ) + // We could add more, but let's keep it simple for now. + //.addDataEntity(...) + //.addContextualEntity(...) + //... + .build(); + + assertNotNull(myFirstCrate); + HelpFunctions.prettyPrintJsonString(myFirstCrate.getJsonMetadata()); + } + + /** + * The library currently comes with three specialized DataEntities: + *

+ * 1. `DataSetEntity` + * 2. `FileEntity` (used in the example above) + * 3. `WorkflowEntity` + *

+ * If another type of `DataEntity` is required, + * the base class `DataEntity` can be used. Example: + */ + @Test + void specializingYourFirstEntity() { + RoCrate crate = STARTER_CRATE + .addDataEntity( + // Let's do something custom: + new DataEntity.DataEntityBuilder() + // You need to add the type of your `DataEntity` + // because for DataEntity, there is no default. + .addType("CreativeWork") + .setId("myEntityInstance") + // Now that we are a CreativeWork instance, + // it is fine to use some of its properties. + .addProperty("https://schema.org/award", "Wow-award") + .build() + ) + .build(); + + assertNotNull(crate); + HelpFunctions.prettyPrintJsonString(crate.getJsonMetadata()); + } + + /** + * A `DataEntity` and its subclasses can have a file located on the web. + * In this case, it does not need to reside in a crate's folder. + * This can be useful for large, publicly available files, + * or in order to reuse or share files. + *

+ * Note: Technically, an entity pointing to a file on the web is just an entity + * that uses the URL as an ID. + */ + @Test + void referencingFilesOnTheWeb() { + // Let's say this is the file we would like to point at with an entity. + String lovelyFile = "https://github.com/kit-data-manager/ro-crate-java/issues/5"; + + RoCrate crate = STARTER_CRATE + .addDataEntity( + // Build our entity to point to the file: + new FileEntity.FileEntityBuilder() + // Make it point to an external file. + .setLocation(URI.create(lovelyFile)) + // This would do the same: + .setId(lovelyFile) + // don't forget to add metadata! + .addProperty("description", "my new file that I added") + .build() + ) + .build(); + + assertNotNull(crate); + HelpFunctions.prettyPrintJsonString(crate.getJsonMetadata()); + } + + /** + * A `DataEntity` and its subclasses can have a local file associated with them, + * instead of one located on the web. + * + * @param tempDir We'll use this to create a temporary folder for our crate. + * @throws IOException If the file cannot be created or written to. + */ + @Test + void includingFilesIntoTheCrateFolder(@TempDir Path tempDir) throws IOException { + // Let's say this is the file we would like to point at with an entity. + String lovelyFile = tempDir.resolve("my/experiment.csv").toString(); + { + // (Let's quickly create a dummy file, but the rest will not make use of this knowledge.) + File lovelyFilePointer = new File(lovelyFile); + FileUtils.touch(lovelyFilePointer); + FileUtils.write(lovelyFilePointer, "My great experiment 001", "UTF-8"); + } + + // But in the crate we want it to be + String seriousExperimentFile = "fantastic-experiment/2025-01-01.csv"; + + RoCrate crate = STARTER_CRATE + .addDataEntity( + // Build our entity to point to the file: + new FileEntity.FileEntityBuilder() + // Let's tell the library where to find and copy the file from. + .setLocation(Paths.get(lovelyFile)) + // Let's tell it to adjust the file name and path in the crate. + .setId(seriousExperimentFile) + .addProperty("description", "my new local file that I added") + .build() + ) + .build(); + + assertNotNull(crate); + HelpFunctions.prettyPrintJsonString(crate.getJsonMetadata()); + + // Let's write it to disk and see if the file is there! + // (We'll discuss writing and reading crates later on.) + Path crateFolder = tempDir.resolve("myCrate"); + Writers.newFolderWriter().save(crate, crateFolder.toString()); + assertTrue(crateFolder.resolve(seriousExperimentFile).toFile().exists()); + } + + /** + * Contextual entities cannot be associated with a file: they are pure metadata + * To add a contextual entity to a crate you use the function + * {@link RoCrate.RoCrateBuilder#addContextualEntity(ContextualEntity)}. + *

+ * Some types of derived/specializes entities are: + *

+ * 1. `OrganizationEntity` + * 2. `PersonEntity` + * 3. `PlaceEntity` + *

+ * If you need another type of contextual entity, use the base class + * {@link ContextualEntity}, similar to how we did it in + * {@link #specializingYourFirstEntity()}. + *

+ * The library provides a way to automatically create contextual entities from + * external providers. Currently, support for [ORCID](https://orcid.org/) and + * [ROR](https://ror.org/) is implemented. + * Check the module {@link edu.kit.datamanager.ro_crate.externalproviders} for + * more implementations. + */ + @Test + void addingContextualEntities() { + PersonEntity person = OrcidProvider.getPerson("https://orcid.org/0000-0001-6575-1022"); + OrganizationEntity organization = RorProvider.getOrganization("https://ror.org/04t3en479"); + + RoCrate crate = STARTER_CRATE + .addContextualEntity(person) + .addContextualEntity(organization) + .build(); + + assertNotNull(crate); + HelpFunctions.prettyPrintJsonString(crate.getJsonMetadata()); + } + + /** + * RO-Crates are file based, but in your application you may want to create a crate + * on the fly and directly send it somewhere else without storing it on disk. + * This is why we can't only write to a folder or a zip file, but also to a stream + * (containing the zip file). + *

+ * There is a generic interface to implement Writers (and Readers), so even more + * exotic use cases should be possible. The readers work the same way. + *

+ * - {@link GenericWriterStrategy} + * - {@link GenericReaderStrategy} + */ + @Test + void writingAndReadingCrates(@TempDir Path tempDir) throws IOException { + // Ok lets make a small, but not fully boring crate. + PersonEntity person = OrcidProvider.getPerson("https://orcid.org/0000-0001-6575-1022"); + OrganizationEntity organization = RorProvider.getOrganization("https://ror.org/04t3en479"); + + RoCrate crate = STARTER_CRATE + .addContextualEntity(person) + .addContextualEntity(organization) + .build(); + + assertNotNull(crate); + HelpFunctions.prettyPrintJsonString(crate.getJsonMetadata()); + + { + // Now, let's write it to a folder. + Path folder = tempDir.resolve("folderCrate"); + Writers.newFolderWriter() + .save(crate, folder.toString()); + // and read it back. + RoCrate read = Readers.newFolderReader() + .readCrate(folder.toAbsolutePath().toString()); + + HelpFunctions.compareTwoCrateJson(crate, read); + } + + { + // Now, let's write it to a zip file. + Path zipFile = tempDir.resolve("zipCrate.zip"); + Writers.newZipPathWriter() + .save(crate, zipFile.toString()); + // and read it back. + RoCrate read = Readers.newZipPathReader() + .readCrate(zipFile.toAbsolutePath().toString()); + + HelpFunctions.compareTwoCrateJson(crate, read); + } + + { + // Now, let's write it to a zip stream. + Path zipStreamFile = tempDir.resolve("zipStreamCrate.zip"); + try (OutputStream outputStream = new FileOutputStream(zipStreamFile.toFile())) { + Writers.newZipStreamWriter().save(crate, outputStream); + } + // and read it back. + try (InputStream inputStream = new FileInputStream(zipStreamFile.toFile())) { + RoCrate read = Readers.newZipStreamReader() + .readCrate(inputStream); + + HelpFunctions.compareTwoCrateJson(crate, read); + } + } + } + + /** + * In {@link #writingAndReadingCrates(Path)} we already saw how to write or read + * a crate. We used the Readers and Writers classes to get the available options. + * But what if you want to write your own reader or writer strategy? + *

+ * Let's see how you can make a reader or writer, manually configuring the strategy. + */ + @Test + void writingAndReadingStrategies(@TempDir Path tempDir) throws IOException { + // Ok lets make a small, but not fully boring crate. + PersonEntity person = OrcidProvider.getPerson("https://orcid.org/0000-0001-6575-1022"); + OrganizationEntity organization = RorProvider.getOrganization("https://ror.org/04t3en479"); + + RoCrate crate = STARTER_CRATE + .addContextualEntity(person) + .addContextualEntity(organization) + .build(); + + assertNotNull(crate); + HelpFunctions.prettyPrintJsonString(crate.getJsonMetadata()); + + // Now, let's write it to a folder. Note the used strategy could be replaced with your own. + Path folder = tempDir.resolve("folderCrate"); + new CrateWriter<>(new FolderStrategy()) + .save(crate, folder.toString()); + // and read it back. + RoCrate read = new CrateReader<>( + // Note: There are two FolderStrategy implementations, one for reading and one for writing. + // Java is a bit bad with imports, so we use the fully qualified name here. + new edu.kit.datamanager.ro_crate.reader.FolderStrategy() + ) + .readCrate(folder.toAbsolutePath().toString()); + + HelpFunctions.compareTwoCrateJson(crate, read); + } + + /** + * RO-Crate specified there should be a human-readable preview of the crate. + * This is a HTML file that can be opened in a browser. + * ro-crate-java offers three different ways to create this file: + *

+ * - AutomaticPreview: Uses third-party library + * ro-crate-html-js, + * which must be installed separately via `npm install --global ro-crate-html-js`. + *

+ * - CustomPreview: Pure Java-based preview using an included template processed by + * the FreeMarker template engine. At the same time, CustomPreview is the fallback + * for AutomaticPreview if ro-crate-html-js is not installed. + *

+ * - StaticPreview: Allows to provide a static HTML page (including additional + * dependencies, e.g., CSS, JS) which is then shipped with the RO-Crate. + *

+ * When creating a new RO-Crate using the builder, the default setting is to use + * CustomPreview. This example shows you how to change it. + */ + @Test + void humanReadableContent() { + RoCrate crate = STARTER_CRATE + .setPreview(new AutomaticPreview()) + .build(); + + assertNotNull(crate); + } + + /** + * A static preview means you'll just add your own HTML file to the crate. + * Therefore, the constructor is a bit more complicated. + */ + @Test + void staticPreview(@TempDir Path tempDir) { + File mainPreviewHtml = tempDir.resolve("mainPreview.html").toFile(); + File additionalFilesDirectory = tempDir.resolve("additionalFiles").toFile(); + + RoCrate crate = STARTER_CRATE + .setPreview(new StaticPreview(mainPreviewHtml, additionalFilesDirectory)) + .build(); + + assertNotNull(crate); + } + + /** + * Crates can be validated. + * Right now, the only implemented way of validating a RO-crate is to use a + * [JSON-Schema](https://json-schema.org/) that the crate's metadata JSON file should + * match. JSON-Schema is an established standard and therefore a good choice for a + * crate profile. This example shows how to use it. + *

+ * Note: If you happen to implement your own validator anyway, please consider + * contributing your code! + */ + @Test + void validation() { + // Let's find a schema file in the resources folder. + URL schemaUrl = Objects.requireNonNull(this.getClass().getResource("/crates/validation/workflowschema.json")); + String schemaPath = schemaUrl.getPath(); + + // This crate for sure is not a workflow, so validation will fail. + RoCrate crate = STARTER_CRATE.build(); + + // And now do the validation. + Validator validator = new Validator(new JsonSchemaValidation(schemaPath)); + assertFalse(validator.validate(crate)); + } +} diff --git a/src/test/resources/spec-v1.1-example-json-files/complete-workflow-example.json b/src/test/resources/spec-v1.1-example-json-files/complete-workflow-example.json new file mode 100644 index 00000000..011258f6 --- /dev/null +++ b/src/test/resources/spec-v1.1-example-json-files/complete-workflow-example.json @@ -0,0 +1,113 @@ +{ "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, + "about": {"@id": "./"} + }, + { + "@id": "./", + "@type": "Dataset", + "hasPart": [ + { "@id": "workflow/alignment.knime" } + ] + }, + { + "@id": "workflow/alignment.knime", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "conformsTo": + {"@id": "https://bioschemas.org/profiles/ComputationalWorkflow/0.5-DRAFT-2020_07_21/"}, + "name": "Sequence alignment workflow", + "programmingLanguage": {"@id": "#knime"}, + "creator": {"@id": "#alice"}, + "dateCreated": "2020-05-23", + "license": { "@id": "https://spdx.org/licenses/CC-BY-NC-SA-4.0"}, + "input": [ + { "@id": "#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b"} + ], + "output": [ + { "@id": "#6c703fee-6af7-4fdb-a57d-9e8bc4486044"}, + { "@id": "#2f32b861-e43c-401f-8c42-04fd84273bdf"} + ], + "sdPublisher": {"@id": "#workflow-hub"}, + "url": "http://example.com/workflows/alignment", + "version": "0.5.0" + }, + { + "@id": "#36aadbd4-4a2d-4e33-83b4-0cbf6a6a8c5b", + "@type": "FormalParameter", + "conformsTo": {"@id": "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/"}, + "name": "genome_sequence", + "valueRequired": true, + "additionalType": {"@id": "http://edamontology.org/data_2977"}, + "format": {"@id": "http://edamontology.org/format_1929"} + }, + { + "@id": "#6c703fee-6af7-4fdb-a57d-9e8bc4486044", + "@type": "FormalParameter", + "conformsTo": {"@id": "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/"}, + "name": "cleaned_sequence", + "additionalType": {"@id": "http://edamontology.org/data_2977"}, + "encodingFormat": {"@id": "http://edamontology.org/format_2572"} + }, + { + "@id": "#2f32b861-e43c-401f-8c42-04fd84273bdf", + "@type": "FormalParameter", + "conformsTo": {"@id": "https://bioschemas.org/profiles/FormalParameter/0.1-DRAFT-2020_07_21/"}, + "name": "sequence_alignment", + "additionalType": {"@id": "http://edamontology.org/data_1383"}, + "encodingFormat": {"@id": "http://edamontology.org/format_1982"} + }, + { + "@id": "https://spdx.org/licenses/CC-BY-NC-SA-4.0", + "@type": "CreativeWork", + "name": "Creative Commons Attribution Non Commercial Share Alike 4.0 International", + "alternateName": "CC-BY-NC-SA-4.0" + }, + { + "@id": "#knime", + "@type": "ComputerLanguage", + "name": "KNIME Analytics Platform", + "alternateName": "KNIME", + "url": "https://www.knime.com/whats-new-in-knime-41", + "version": "4.1.3" + }, + { + "@id": "#alice", + "@type": "Person", + "name": "Alice Brown" + }, + { + "@id": "#workflow-hub", + "@type": "Organization", + "name": "Example Workflow Hub", + "url":"http://example.com/workflows/" + }, + { + "@id": "http://edamontology.org/format_1929", + "@type": "Thing", + "name": "FASTA sequence format" + }, + { + "@id": "http://edamontology.org/format_1982", + "@type": "Thing", + "name": "ClustalW alignment format" + }, + { + "@id": "http://edamontology.org/format_2572", + "@type": "Thing", + "name": "BAM format" + }, + { + "@id": "http://edamontology.org/data_2977", + "@type": "Thing", + "name": "Nucleic acid sequence" + }, + { + "@id": "http://edamontology.org/data_1383", + "@type": "Thing", + "name": "Nucleic acid sequence alignment" + } + ] +} diff --git a/src/test/resources/spec-v1.1-example-json-files/file-author-location.json b/src/test/resources/spec-v1.1-example-json-files/file-author-location.json new file mode 100644 index 00000000..6bd83070 --- /dev/null +++ b/src/test/resources/spec-v1.1-example-json-files/file-author-location.json @@ -0,0 +1,47 @@ +{ "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, + "about": {"@id": "./"}, + "description": "RO-Crate Metadata File Descriptor (this file)" + }, + { + "@id": "./", + "@type": "Dataset", + "name": "Example RO-Crate", + "description": "The RO-Crate Root Data Entity", + "hasPart": [ + {"@id": "data1.txt"}, + {"@id": "data2.txt"} + ] + }, + + + { + "@id": "data1.txt", + "@type": "File", + "description": "One of hopefully many Data Entities", + "author": {"@id": "#alice"}, + "contentLocation": {"@id": "http://sws.geonames.org/8152662/"} + }, + { + "@id": "data2.txt", + "@type": "File" + }, + + { + "@id": "#alice", + "@type": "Person", + "name": "Alice", + "description": "One of hopefully many Contextual Entities" + }, + { + "@id": "http://sws.geonames.org/8152662/", + "@type": "Place", + "name": "Catalina Park" + } + ] +} diff --git a/src/test/resources/spec-v1.1-example-json-files/files-and-folders.json b/src/test/resources/spec-v1.1-example-json-files/files-and-folders.json new file mode 100644 index 00000000..e8bb2ab0 --- /dev/null +++ b/src/test/resources/spec-v1.1-example-json-files/files-and-folders.json @@ -0,0 +1,38 @@ +{ "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, + "about": {"@id": "./"} + }, + { + "@id": "./", + "@type": [ + "Dataset" + ], + "hasPart": [ + { + "@id": "cp7glop.ai" + }, + { + "@id": "lots_of_little_files/" + } + ] + }, + { + "@id": "cp7glop.ai", + "@type": "File", + "name": "Diagram showing trend to increase", + "contentSize": "383766", + "description": "Illustrator file for Glop Pot", + "encodingFormat": "application/pdf" + }, + { + "@id": "lots_of_little_files/", + "@type": "Dataset", + "name": "Too many files", + "description": "This directory contains many small files, that we're not going to describe in detail." + } + ] +} diff --git a/src/test/resources/spec-v1.1-example-json-files/minimal.json b/src/test/resources/spec-v1.1-example-json-files/minimal.json new file mode 100644 index 00000000..3aee3b8c --- /dev/null +++ b/src/test/resources/spec-v1.1-example-json-files/minimal.json @@ -0,0 +1,27 @@ +{ "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, + "about": {"@id": "./"} + }, + { + "@id": "./", + "identifier": "https://doi.org/10.4225/59/59672c09f4a4b", + "@type": "Dataset", + "datePublished": "2017", + "name": "Data files associated with the manuscript:Effects of facilitated family case conferencing for ...", + "description": "Palliative care planning for nursing home residents with advanced dementia ...", + "license": {"@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/"} + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "CreativeWork", + "description": "This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/au/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.", + "identifier": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "name": "Attribution-NonCommercial-ShareAlike 3.0 Australia (CC BY-NC-SA 3.0 AU)" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/spec-v1.1-example-json-files/web-based-data-entities.json b/src/test/resources/spec-v1.1-example-json-files/web-based-data-entities.json new file mode 100644 index 00000000..d5245907 --- /dev/null +++ b/src/test/resources/spec-v1.1-example-json-files/web-based-data-entities.json @@ -0,0 +1,39 @@ +{ "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, + "about": {"@id": "./"} + }, + { + "@id": "./", + "@type": [ + "Dataset" + ], + "hasPart": [ + { + "@id": "survey-responses-2019.csv" + }, + { + "@id": "https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf" + } + ] + }, + { + "@id": "survey-responses-2019.csv", + "@type": "File", + "name": "Survey responses", + "contentSize": "26452", + "encodingFormat": "text/csv" + }, + { + "@id": "https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf", + "@type": "File", + "name": "RO-Crate specification", + "contentSize": "310691", + "description": "RO-Crate specification", + "encodingFormat": "application/pdf" + } + ] +}