diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 38f6634f..ba8a9308 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -18,9 +18,7 @@ env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} jobs: - # avoid double CI runs on push and PR, from https://github.com/orgs/community/discussions/57827 build: - if: github.event_name != 'push' || github.event.push.head.repo.full_name != github.event.push.base.repo.full_name strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -49,3 +47,6 @@ jobs: - name: Do one Coveralls test report if: matrix.os == 'ubuntu-latest' && matrix.jdk == 21 run: ./gradlew -Dprofile=release jacocoTestReport coveralls + - name: Compile Javadoc + if: matrix.os == 'ubuntu-latest' && matrix.jdk == 21 + run: ./gradlew javadoc diff --git a/.gitignore b/.gitignore index 68eac014..2e6ac719 100644 --- a/.gitignore +++ b/.gitignore @@ -169,4 +169,7 @@ gradle-app.setting # gradle/wrapper/gradle-wrapper.properties ### Gradle Patch ### +.DS_Store **/build/ +**/jte-classes +**/temp diff --git a/README.md b/README.md index 47f6777e..e5613a23 100644 --- a/README.md +++ b/README.md @@ -117,18 +117,24 @@ PersonEntity person = ORCIDProvider.getPerson("https://orcid.org/*") OrganizationEntity organization = RORProvider.getOrganization("https://ror.org/*"); ``` -### Writing Crate to folder or zip file +### Writing Crate to folder, zip file, or zip stream Writing to folder: ```java RoCrateWriter folderRoCrateWriter = new RoCrateWriter(new FolderWriter()); -folderRoCrateWriter.save(roCrate, "destination"); +folderRoCrateWriter.save(roCrate, "destinationFolder"); ``` Writing to zip file: ```java RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); -roCrateZipWriter.save(roCrate, "destination"); +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. @@ -138,25 +144,48 @@ More writing strategies can be implemented, if required. Reading from folder: ```java RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); -RoCrate res = roCrateFolderReader.readCrate("source"); +RoCrate res = roCrateFolderReader.readCrate("destinationFolder"); ``` Reading from zip file: ```java RoCrateReader roCrateFolderReader = new RoCrateReader(new ZipReader()); -RoCrate crate = roCrateFolderReader.readCrate("source"); +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) -By setting the preview to an `AutomaticPreview`, the library will automatically create a preview using the [ro-crate-html-js](https://www.npmjs.com/package/ro-crate-html-js) tool. -It has to be installed using `npm install --global ro-crate-html-js` in order to use it. -If you want to use a custom-made preview, you can set it using the `CustomPreview` class. `AutomaticPreview` is currently **not** set by default. +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()) .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. + +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: @@ -395,8 +424,6 @@ The web resource does not use `.setSource()`, but uses the ID to indicate the fi {"@id": "data2.txt"} ] }, - - { "@id": "data1.txt", "@type": "File", @@ -637,7 +664,6 @@ If there is no special method for including relative entities (ID properties) on .addIdProperty("additionalType", nucSec) .addIdProperty("encodingFormat", fasta) .build(); - ContextualEntity clnParam = new ContextualEntity.ContextualEntityBuilder() .addType("FormalParameter") .setId("#6c703fee-6af7-4fdb-a57d-9e8bc4486044") @@ -646,7 +672,6 @@ If there is no special method for including relative entities (ID properties) on .addIdProperty("additionalType", nucSec) .addIdProperty("encodingFormat", ban) .build(); - ContextualEntity alignParam = new ContextualEntity.ContextualEntityBuilder() .addType("FormalParameter") .setId("#2f32b861-e43c-401f-8c42-04fd84273bdf") diff --git a/build.gradle b/build.gradle index 2c87b3b1..cd6c189f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'jvm-test-suite' - + id 'application' id 'jacoco' // Adds coveralls task for CI to send results to the coveralls service. id "com.github.kt3k.coveralls" version "2.12.2" @@ -13,7 +13,7 @@ plugins { // Publishing of JAR to Nexus instances (e.g., OSSRH) // https://github.com/gradle-nexus/publish-plugin id "io.github.gradle-nexus.publish-plugin" version "2.0.0" - id "io.freefair.maven-publish-java" version "8.13" + id "io.freefair.maven-publish-java" version "8.13.1" } group 'edu.kit.datamanager' @@ -24,6 +24,9 @@ println "Building ${name} version: ${version}" println "JDK version: ${JavaVersion.current()}" println "Profile (system property): ${System.getProperty('profile')}" +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + if (JavaVersion.current() == JavaVersion.VERSION_17) { println "Setting encoding to UTF-8 manually" compileJava.options.encoding = "UTF-8" @@ -40,7 +43,7 @@ ext { dependencies { // JUnit setup for testing - testImplementation(platform("org.junit:junit-bom:5.12.1")) + testImplementation(platform("org.junit:junit-bom:5.12.2")) testImplementation('org.junit.jupiter:junit-jupiter') testRuntimeOnly('org.junit.platform:junit-platform-launcher') // JSON object mapping / (de-)serialization @@ -49,11 +52,11 @@ dependencies { // http client implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14' // common file system operations - implementation group: 'commons-io', name: 'commons-io', version: '2.18.0' + implementation group: 'commons-io', name: 'commons-io', version: '2.19.0' // read from and write to zip files implementation group: 'net.lingala.zip4j', name: 'zip4j', version: '2.11.5' // compare json documents in tests - implementation 'com.github.fslev:json-compare:6.18' + implementation 'com.github.fslev:json-compare:7.0' // url validator implementation group: 'commons-validator', name: 'commons-validator', version: '1.9.0' // logging @@ -63,8 +66,13 @@ dependencies { // metadata validation, profiles based on JSON schema implementation group: "com.networknt", name: "json-schema-validator", version: "1.5.6" implementation 'org.glassfish:jakarta.json:2.0.1' + //JTE for template processing + implementation('gg.jte:jte:3.2.0') + implementation("org.freemarker:freemarker:2.3.34") } +logging.captureStandardOutput LogLevel.INFO + def signingTasks = tasks.withType(Sign) tasks.withType(AbstractPublishToMaven).configureEach{ mustRunAfter(signingTasks) @@ -155,7 +163,7 @@ jacocoTestReport { } jacoco { - toolVersion = "0.8.12" + toolVersion = "0.8.13" } // maxParallelForks(2) diff --git a/gradle.properties b/gradle.properties index ade582a7..ec6fa2a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,7 @@ -version=2.0.3 \ No newline at end of file +version=2.0.3 +action.custom-1=install +action.custom-1.args=--configure-on-demand -w -x check clean publishToMavenLocal +action.custom-2=jacoco +action.custom-2.args=--configure-on-demand -w clean check jacocoTestReport +action.custom-3=releaseNewVersion +action.custom-3.args=--configure-on-demand -w -x check -Prelease release \ No newline at end of file diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json new file mode 100644 index 00000000..f86459e3 --- /dev/null +++ b/ro-crate-metadata.json @@ -0,0 +1,22 @@ +{ + "@context" : "https://w3id.org/ro/crate/1.1/context", + "@graph" : [ { + "name" : "name", + "description" : "description", + "@id" : "./", + "@type" : "Dataset", + "license" : { + "@id" : "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "datePublished" : "2024" + }, { + "about" : { + "@id" : "./" + }, + "conformsTo" : { + "@id" : "https://w3id.org/ro/crate/1.1" + }, + "@id" : "ro-crate-metadata.json", + "@type" : "CreativeWork" + } ] +} \ No newline at end of file diff --git a/src/main/java/edu/kit/datamanager/ro_crate/Crate.java b/src/main/java/edu/kit/datamanager/ro_crate/Crate.java index d7fd8bd9..aff1b6fe 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/Crate.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/Crate.java @@ -2,6 +2,7 @@ import java.io.File; import java.util.Collection; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -51,6 +52,25 @@ public interface Crate { void setMetadataContext(CrateMetadataContext metadataContext); + /** + * Get the value of a key from the metadata context. + * @param key the key to be searched + * @return the value of the key, null if not found + */ + String getMetadataContextValueOf(String key); + + /** + * Get an immutable collection of the keys in the metadata context. + * @return the keys in the metadata context + */ + Set getMetadataContextKeys(); + + /** + * Get an immutable map of the context. + * @return an immutable map containing the context key-value pairs + */ + Map getMetadataContextPairs(); + RootDataEntity getRootDataEntity(); void setRootDataEntity(RootDataEntity rootDataEntity); 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 ea6911e0..330135b7 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java @@ -10,20 +10,28 @@ 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; import edu.kit.datamanager.ro_crate.preview.CratePreview; +import edu.kit.datamanager.ro_crate.preview.CustomPreview; import edu.kit.datamanager.ro_crate.special.CrateVersion; 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; @@ -57,22 +65,41 @@ public void setRoCratePreview(CratePreview preview) { this.roCratePreview = preview; } + @Override public void setMetadataContext(CrateMetadataContext metadataContext) { this.metadataContext = metadataContext; } + @Override + public String getMetadataContextValueOf(String key) { + return this.metadataContext.getValueOf(key); + } + + @Override + public Set getMetadataContextKeys() { + return this.metadataContext.getKeys(); + } + + @Override + public Map getMetadataContextPairs() { + return this.metadataContext.getPairs(); + } + public ContextualEntity getJsonDescriptor() { return jsonDescriptor; } + @Override public void setJsonDescriptor(ContextualEntity jsonDescriptor) { this.jsonDescriptor = jsonDescriptor; } - + + @Override public RootDataEntity getRootDataEntity() { return rootDataEntity; } - + + @Override public void setRootDataEntity(RootDataEntity rootDataEntity) { this.rootDataEntity = rootDataEntity; } @@ -184,8 +211,8 @@ public AbstractEntity getEntityById(String id) { /** * {@inheritDoc} *

- * Note: This will also link the DataEntity to the root node - * using the root nodes hasPart property. + * Note: This will also link the DataEntity to the root node using the root + * nodes hasPart property. * * @param entity the DataEntity to add to this crate. */ @@ -251,7 +278,7 @@ public static class RoCrateBuilder { private static final String PROPERTY_DESCRIPTION = "description"; CratePayload payload; - CratePreview preview; + CratePreview preview = new CustomPreview(); CrateMetadataContext metadataContext; ContextualEntity license; RootDataEntity rootDataEntity; @@ -335,8 +362,8 @@ public RoCrateBuilder addDescription(String description) { /** * Adds a data entity to the crate. *

- * Note: This will also link the DataEntity to the root node - * using the root nodes hasPart property. + * Note: This will also link the DataEntity to the root node using the + * root nodes hasPart property. * * @param dataEntity the DataEntity to add to this crate. * @return returns the builder for further usage. @@ -372,21 +399,22 @@ public RoCrateBuilder setLicense(ContextualEntity license) { /** * Setting the license of the crate using only a license identifier. * - * @param licenseId the licenses identifier. Should be a resolveable URI. + * @param licenseId the licenses identifier. Should be a resolveable + * URI. * @return the builder */ public RoCrateBuilder setLicense(String licenseId) { ContextualEntity licenseEntity = new ContextualEntity.ContextualEntityBuilder() - .setId(licenseId) - .build(); + .setId(licenseId) + .build(); this.setLicense(licenseEntity); return this; } /** - * Adds a property with date time format. The property should match the ISO 8601 - * date format. - * + * Adds a property with date time format. The property should match the + * ISO 8601 date format. + * * @param dateValue time string in ISO 8601 format * @return this builder * @throws IllegalArgumentException if format is not ISO 8601 @@ -458,7 +486,8 @@ public BuilderWithDraftFeatures(String name, String description, String datePubl } /** - * @see RoCrateBuilder#RoCrateBuilder(String, String, String, ContextualEntity) + * @see RoCrateBuilder#RoCrateBuilder(String, String, String, + * ContextualEntity) */ public BuilderWithDraftFeatures(String name, String description, String datePublished, ContextualEntity licenseId) { super(name, description, datePublished, licenseId); diff --git a/src/main/java/edu/kit/datamanager/ro_crate/context/CrateMetadataContext.java b/src/main/java/edu/kit/datamanager/ro_crate/context/CrateMetadataContext.java index ba203338..dfe6548e 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/context/CrateMetadataContext.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/context/CrateMetadataContext.java @@ -4,6 +4,9 @@ import edu.kit.datamanager.ro_crate.entities.AbstractEntity; +import java.util.Map; +import java.util.Set; + /** * Interface for the metadata context. * Most often in an ROCrate this is the default context, @@ -22,6 +25,25 @@ public interface CrateMetadataContext { void addToContext(String key, String value); + /** + * Get the value of a key from the context. + * @param key the key to be searched + * @return the value of the key, null if not found + */ + String getValueOf(String key); + + /** + * Get an immutable collection of the keys in the metadata context. + * @return the keys in the metadata context + */ + Set getKeys(); + + /** + * Get an immutable map of the context. + * @return an immutable map containing the context key-value pairs + */ + Map getPairs(); + void deleteValuePairFromContext(String key); void deleteUrlFromContext(String url); diff --git a/src/main/java/edu/kit/datamanager/ro_crate/context/RoCrateMetadataContext.java b/src/main/java/edu/kit/datamanager/ro_crate/context/RoCrateMetadataContext.java index f4c2dfc4..731a4c1a 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/context/RoCrateMetadataContext.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/context/RoCrateMetadataContext.java @@ -31,7 +31,7 @@ public class RoCrateMetadataContext implements CrateMetadataContext { protected static final String DEFAULT_CONTEXT_LOCATION = "default_context/version1.1.json"; protected static JsonNode defaultContext = null; - protected final Set url = new HashSet<>(); + protected final Set urls = new HashSet<>(); protected final HashMap contextMap = new HashMap<>(); // we need to keep the ones that are no coming from url // for the final representation @@ -89,7 +89,7 @@ public ObjectNode getContextJsonEntity() { ArrayNode array = objectMapper.createArrayNode(); ObjectNode jsonNode = objectMapper.createObjectNode(); ObjectNode finalNode = objectMapper.createObjectNode(); - for (String e : url) { + for (String e : urls) { array.add(e); } for (Map.Entry s : other.entrySet()) { @@ -156,7 +156,7 @@ public boolean checkEntity(AbstractEntity entity) { @Override public void addToContextFromUrl(String url) { - this.url.add(url); + this.urls.add(url); ObjectMapper objectMapper = MyObjectMapper.getMapper(); @@ -200,6 +200,29 @@ public void addToContext(String key, String value) { this.other.put(key, value); } + @Override + public String getValueOf(String key) { + return Optional.ofNullable(this.contextMap.get(key)) + .orElseGet(() -> this.other.get(key)); + } + + @Override + public Set getKeys() { + List merged = new ArrayList<>(); + merged.addAll(this.contextMap.keySet()); + merged.addAll(this.other.keySet()); + return Set.copyOf(merged); + } + + @Override + public Map getPairs() { + Map merged = new HashMap<>(); + merged.putAll(this.contextMap); + merged.putAll(this.other); + return Map.copyOf(merged); + } + + @Override public void deleteValuePairFromContext(String key) { this.contextMap.remove(key); @@ -208,7 +231,7 @@ public void deleteValuePairFromContext(String key) { @Override public void deleteUrlFromContext(String url) { - this.url.remove(url); + this.urls.remove(url); } } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataEntity.java b/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataEntity.java index 28293a54..e6e28f8f 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataEntity.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataEntity.java @@ -5,6 +5,7 @@ import edu.kit.datamanager.ro_crate.entities.AbstractEntity; import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity; import static edu.kit.datamanager.ro_crate.special.IdentifierUtils.isUrl; +import edu.kit.datamanager.ro_crate.util.ZipUtil; import java.io.File; import java.io.IOException; @@ -14,6 +15,7 @@ import java.util.List; import net.lingala.zip4j.ZipFile; import net.lingala.zip4j.exception.ZipException; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; import net.lingala.zip4j.model.ZipParameters; import org.apache.commons.io.FileUtils; @@ -44,11 +46,12 @@ public DataEntity(AbstractDataEntityBuilder entityBuilder) { /** * Adds an author ID to the entity. - * + *

* Calling this multiple times will add multiple author IDs. - * + * * @param id the identifier of the author. */ + @SuppressWarnings("unused") public void addAuthorId(String id) { this.addIdProperty("author", id); } @@ -69,6 +72,21 @@ public void saveToZip(ZipFile zipFile) throws ZipException { } } + /** + * If the data entity contains a physical file. This method will write it + * when the crate is being written to a zip archive. + * + * @param zipStream The zip output stream where it should be written. + * @throws ZipException when something goes wrong with the writing to the + * zip file. + * @throws IOException If opening the file input stream fails. + */ + public void saveToStream(ZipOutputStream zipStream) throws ZipException, IOException { + if (this.path != null) { + ZipUtil.addFileToZipStream(zipStream, this.path.toFile(), this.getId()); + } + } + /** * If the data entity contains a physical file. This method will write it * when the crate is being written to a folder. @@ -98,15 +116,19 @@ abstract static class AbstractDataEntityBuilder authors = new ArrayList<>(); /** - * Sets the location of the data entity. - * - * If the ID has not been set manually in beforehand, it will be derived from - * the path. Use {@link #setId(String)} to override it or set it in beforehand. - * Note that another call of {@link #setLocation(Path)} will not override the ID - * as it has been set by the previous call! + * Sets the location of the data entity to make a copy from when writing the crate. + *

+ * Use this if you want to copy the associated file to the crate. + * Do not use it if the file should already be considered (e.g. + * as part of a DataSetEntity). + *

+ * If the ID has not been set manually in beforehand, it will be derived + * from the path. Use {@link #setId(String)} to override it or set it in + * beforehand. Note that another call of this method will + * not override the ID as it has been set by the previous call! * - * @param path the location of the data. May be null, in which case nothing - * happens. + * @param path the location of the data. May be null, in which case + * nothing happens. * @return this builder */ public T setLocation(Path path) { @@ -134,13 +156,14 @@ public T setLocationWithExceptions(Path path) throws IllegalArgumentException { } /** - * Same as {@link #setLocation(Path)} but instead of associating this entity - * with a file, it will point to some place on the internet. - * - * Via the specification, this means the uri will be set as the ID. This call is - * therefore equivalent to {@link #setId(String)}. - * - * @param uri the URI, should point at the data reachable on the internet. + * Same as {@link #setLocation(Path)} but instead of associating this + * entity with a file, it will point to some place on the internet. + *

+ * Via the specification, this means the uri will be set as the ID. This + * call is therefore equivalent to {@link #setId(String)}. + * + * @param uri the URI, should point at the data reachable on the + * internet. * @return this builder */ public T setLocation(URI uri) { @@ -190,10 +213,10 @@ public T setContentLocation(String id) { /** * Data Entity builder class that allows for easier data entity creation. - * - * If not explicitly mentioned, all methods avoid Exceptions and will silently - * ignore null-parameters, in which case nothing will happen. Use the available - * *WithExceptions-methods in case you need them. + *

+ * If not explicitly mentioned, all methods avoid Exceptions and will + * silently ignore null-parameters, in which case nothing will happen. Use + * the available *WithExceptions-methods in case you need them. */ public static final class DataEntityBuilder extends AbstractDataEntityBuilder { diff --git a/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataSetEntity.java b/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataSetEntity.java index c9f08d3f..832d9819 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataSetEntity.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/entities/data/DataSetEntity.java @@ -4,11 +4,15 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import edu.kit.datamanager.ro_crate.entities.serializers.HasPartSerializer; +import edu.kit.datamanager.ro_crate.util.ZipUtil; +import java.io.IOException; import java.util.HashSet; import java.util.Set; import net.lingala.zip4j.ZipFile; import net.lingala.zip4j.exception.ZipException; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; +import net.lingala.zip4j.model.ZipParameters; /** * A helping class for the creating of Data entities of type Dataset. @@ -18,90 +22,102 @@ */ public class DataSetEntity extends DataEntity { - public static final String TYPE = "Dataset"; - - @JsonSerialize(using = HasPartSerializer.class) - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public Set hasPart; - - /** - * Constructor for instantiating a Dataset entity from the builder. - * - * @param entityBuilder the builder passed as argument. - */ - public DataSetEntity(AbstractDataSetBuilder entityBuilder) { - super(entityBuilder); - this.hasPart = entityBuilder.hasPart; - this.addType(TYPE); - } - - public void removeFromHasPart(String str) { - this.hasPart.remove(str); - } - - @Override - public void saveToZip(ZipFile zipFile) throws ZipException { - if (this.getPath() != null) { - zipFile.addFolder(this.getPath().toFile()); + public static final String TYPE = "Dataset"; + + @JsonSerialize(using = HasPartSerializer.class) + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public Set hasPart; + + /** + * Constructor for instantiating a Dataset entity from the builder. + * + * @param entityBuilder the builder passed as argument. + */ + public DataSetEntity(AbstractDataSetBuilder entityBuilder) { + super(entityBuilder); + this.hasPart = entityBuilder.hasPart; + this.addType(TYPE); } - } - - public void addToHasPart(String id) { - this.hasPart.add(id); - } - - public boolean hasInHasPart(String id) { - return this.hasPart.contains(id); - } - - abstract static class AbstractDataSetBuilder> extends - AbstractDataEntityBuilder { - - Set hasPart; + public void removeFromHasPart(String str) { + this.hasPart.remove(str); + } - public AbstractDataSetBuilder() { - this.hasPart = new HashSet<>(); + @Override + public void saveToZip(ZipFile zipFile) throws ZipException { + if (this.getPath() != null) { + ZipParameters parameters = new ZipParameters(); + parameters.setRootFolderNameInZip(this.getId()); + parameters.setIncludeRootFolder(false); + zipFile.addFolder(this.getPath().toFile(), parameters); + } } - public T setHasPart(Set hastPart) { - this.hasPart = hastPart; - return self(); + @Override + public void saveToStream(ZipOutputStream zipOutputStream) throws IOException { + if (this.getPath() != null) { + ZipUtil.addFolderToZipStream( + zipOutputStream, + this.getPath().toAbsolutePath().toString(), + this.getId()); + } } - public T addToHasPart(DataEntity dataEntity) { - if (dataEntity != null) { - this.hasPart.add(dataEntity.getId()); - this.relatedItems.add(dataEntity.getId()); - } - return self(); + public void addToHasPart(String id) { + this.hasPart.add(id); } - public T addToHasPart(String dataEntity) { - if (dataEntity != null) { - this.hasPart.add(dataEntity); - this.relatedItems.add(dataEntity); - } - return self(); + public boolean hasInHasPart(String id) { + return this.hasPart.contains(id); } - @Override - public abstract DataSetEntity build(); - } + abstract static class AbstractDataSetBuilder> extends + AbstractDataEntityBuilder { + + Set hasPart; + + public AbstractDataSetBuilder() { + this.hasPart = new HashSet<>(); + } + + public T setHasPart(Set hastPart) { + this.hasPart = hastPart; + return self(); + } + + public T addToHasPart(DataEntity dataEntity) { + if (dataEntity != null) { + this.hasPart.add(dataEntity.getId()); + this.relatedItems.add(dataEntity.getId()); + } + return self(); + } + + public T addToHasPart(String dataEntity) { + if (dataEntity != null) { + this.hasPart.add(dataEntity); + this.relatedItems.add(dataEntity); + } + return self(); + } + + @Override + public abstract DataSetEntity build(); + } - /** - * Builder for the helping class DataSetEntity. - */ - public static final class DataSetBuilder extends AbstractDataSetBuilder { + /** + * Builder for the helping class DataSetEntity. + */ + public static final class DataSetBuilder extends AbstractDataSetBuilder { - @Override - public DataSetBuilder self() { - return this; - } + @Override + public DataSetBuilder self() { + return this; + } - @Override - public DataSetEntity build() { - return new DataSetEntity(this); + @Override + public DataSetEntity build() { + return new DataSetEntity(this); + } } - } } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/entities/validation/JsonSchemaValidation.java b/src/main/java/edu/kit/datamanager/ro_crate/entities/validation/JsonSchemaValidation.java index beaca0fa..067f5e2e 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/entities/validation/JsonSchemaValidation.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/entities/validation/JsonSchemaValidation.java @@ -68,7 +68,7 @@ public boolean validateEntity(JsonNode entity) { @Override public boolean validateFieldOfEntity(JsonNode field) { Set errors = this.entityFieldSchema.validate(field); - if (errors.size() != 0) { + if (!errors.isEmpty()) { ObjectMapper objectMapper = MyObjectMapper.getMapper(); System.err.println("The property: "); try { diff --git a/src/main/java/edu/kit/datamanager/ro_crate/externalproviders/organizationprovider/RorProvider.java b/src/main/java/edu/kit/datamanager/ro_crate/externalproviders/organizationprovider/RorProvider.java index c3158515..350fdad6 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/externalproviders/organizationprovider/RorProvider.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/externalproviders/organizationprovider/RorProvider.java @@ -1,11 +1,14 @@ package edu.kit.datamanager.ro_crate.externalproviders.organizationprovider; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import edu.kit.datamanager.ro_crate.entities.contextual.OrganizationEntity; import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; import java.io.IOException; +import java.util.ArrayList; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; @@ -20,45 +23,63 @@ */ public class RorProvider { - private static Logger logger = LoggerFactory.getLogger(RorProvider.class); + private static final Logger logger = LoggerFactory.getLogger(RorProvider.class); - private RorProvider() {} + private RorProvider() { + } + + /** + * The method that parses a ror entry to a crate entity. + * + * @param url the url of the ror entry. + * @return the created Organization entity. + */ + public static OrganizationEntity getOrganization(String url) { + if (!url.startsWith("https://ror.org/")) { + throw new IllegalArgumentException("Should provide ror url"); + } + String newUrl = "https://api.ror.org/v2/organizations/" + url.replaceAll("https://ror.org/", ""); + HttpGet request = new HttpGet(newUrl); - /** - * The method that parses a ror entry to a crate entity. - * - * @param url the url of the ror entry. - * @return the created Organization entity. - */ - public static OrganizationEntity getOrganization(String url) { - if (!url.startsWith("https://ror.org/")) { - throw new IllegalArgumentException("Should provide ror url"); + try ( + CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(request)) { + boolean isError = response.getStatusLine().getStatusCode() != HttpStatus.SC_OK; + if (isError) { + String errorMessage = String.format("Identifier not found: %s", response.getStatusLine().toString()); + logger.error(errorMessage); + return null; + } + ObjectNode jsonNode = MyObjectMapper.getMapper().readValue(response.getEntity().getContent(), + ObjectNode.class); + + return new OrganizationEntity.OrganizationEntityBuilder() + .setId(jsonNode.path("id").asText()) + .addProperty("name", getOrganizationNameV2(jsonNode.path("names"))) + .addProperty("email", jsonNode.path("email_address")) + .addProperty("url", jsonNode.path("id").asText()) + .build(); + } catch (IOException e) { + String errorMessage = String.format("IO error: %s", e.getMessage()); + logger.error(errorMessage); + } + return null; } - String newUrl = "https://api.ror.org/organizations/" + url.replaceAll("https://ror.org/", ""); - HttpGet request = new HttpGet(newUrl); - try ( - CloseableHttpClient httpClient = HttpClients.createDefault(); - CloseableHttpResponse response = httpClient.execute(request); - ) { - boolean isError = response.getStatusLine().getStatusCode() != HttpStatus.SC_OK; - if (isError) { - String errorMessage = String.format("Identifier not found: %s", response.getStatusLine().toString()); - logger.error(errorMessage); - return null; + private static String getOrganizationNameV2(JsonNode node) { + if (node.isArray()) { + for (JsonNode n : node) { + if (n.has("types") && n.path("types").isArray()) { + ArrayList l = new ObjectMapper().convertValue(n.path("types"), ArrayList.class); + if (l.contains("ror_display")) { + return n.path("value").asText(); + } + } + } + //fallback + return node.path(0).path("value").asText(); + } else { + return node.asText(); } - ObjectNode jsonNode = MyObjectMapper.getMapper().readValue(response.getEntity().getContent(), - ObjectNode.class); - return new OrganizationEntity.OrganizationEntityBuilder() - .setId(jsonNode.get("id").asText()) - .addProperty("name", jsonNode.get("name")) - .addProperty("email", jsonNode.get("email_address")) - .addProperty("url", jsonNode.get("links")) - .build(); - } catch (IOException e) { - String errorMessage = String.format("IO error: %s", e.getMessage()); - logger.error(errorMessage); } - return null; - } + } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/preview/AutomaticPreview.java b/src/main/java/edu/kit/datamanager/ro_crate/preview/AutomaticPreview.java index 7124d203..38241476 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/preview/AutomaticPreview.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/preview/AutomaticPreview.java @@ -1,37 +1,89 @@ package edu.kit.datamanager.ro_crate.preview; +import edu.kit.datamanager.ro_crate.util.ZipUtil; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.exception.ZipException; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; import org.apache.commons.io.FileUtils; /** - * The default preview should use the rochtml tool (https://www.npmjs.com/package/ro-crate-html-js) - * for creating a simple preview file. + * The default preview should use the rochtml tool + * (https://www.npmjs.com/package/ro-crate-html-js) for creating a simple + * preview file. * * @author Nikola Tzotchev on 6.2.2022 г. * @version 1 */ public class AutomaticPreview implements CratePreview { - public AutomaticPreview() { - } - - @Override - public void saveAllToZip(ZipFile zipFile) { - try { - // extract the .json file so we can run the "rochtml" tool on it" - zipFile.extractFile("ro-crate-metadata.json", "temp"); - PreviewGenerator.generatePreview("temp"); - zipFile.addFile("temp/ro-crate-preview.html"); - FileUtils.deleteDirectory(new File("temp")); - } catch (IOException e) { - e.printStackTrace(); + public AutomaticPreview() { + } + + @Override + public void saveAllToZip(ZipFile zipFile) throws IOException { + if (PreviewGenerator.isRochtmlAvailable()) { + // extract the .json file so we can run the "rochtml" tool on it" + try { + try { + zipFile.extractFile("ro-crate-metadata.json", "temp"); + } catch (ZipException ex) { + throw new IOException("ro-crate-metadata.json not found in provided ZIP.", ex); + } + if (PreviewGenerator.isRochtmlAvailable()) { + PreviewGenerator.generatePreview("temp"); + zipFile.addFile("temp/ro-crate-preview.html"); + } + } finally { + try { + FileUtils.deleteDirectory(new File("temp")); + } catch (IOException ex) { + //ignore + } + } + } else { + new CustomPreview().saveAllToZip(zipFile); + } + } + + @Override + public void saveAllToFolder(File folder) throws IOException { + if (folder == null || !folder.exists()) { + throw new IOException("Preview target folder " + folder + " does not exist."); + } + + if (PreviewGenerator.isRochtmlAvailable()) { + PreviewGenerator.generatePreview(folder.getAbsolutePath()); + } else { + new CustomPreview().saveAllToFolder(folder); + } + } + + @Override + public void saveAllToStream(String metadata, ZipOutputStream stream) throws IOException { + if (PreviewGenerator.isRochtmlAvailable()) { + try { + FileUtils.forceMkdir(new File("temp")); + try (FileWriter writer = new FileWriter(new File("temp/ro-crate-metadata.json"))) { + writer.write(metadata); + writer.flush(); + } + if (PreviewGenerator.isRochtmlAvailable()) { + PreviewGenerator.generatePreview("temp"); + ZipUtil.addFileToZipStream(stream, new File("temp/ro-crate-preview.html"), "ro-crate-preview.html"); + } + } finally { + try { + FileUtils.deleteDirectory(new File("temp")); + } catch (IOException ex) { + //ignore + } + } + } else { + new CustomPreview().saveAllToStream(metadata, stream); + } } - } - @Override - public void saveAllToFolder(File folder) { - PreviewGenerator.generatePreview(folder.getAbsolutePath()); - } } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/preview/CratePreview.java b/src/main/java/edu/kit/datamanager/ro_crate/preview/CratePreview.java index 0a225b8e..14ea76ea 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/preview/CratePreview.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/preview/CratePreview.java @@ -1,19 +1,24 @@ package edu.kit.datamanager.ro_crate.preview; import java.io.File; +import java.io.IOException; import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; /** - * Interface for the ROCrate preview. - * This manages the human-readable representation of a crate. + * Interface for the ROCrate preview. This manages the human-readable + * representation of a crate. * * @author Nikola Tzotchev on 6.2.2022 г. - * @version 1 + * @author jejkal + * @version 2 */ public interface CratePreview { - void saveAllToZip(ZipFile zipFile); + void saveAllToZip(ZipFile zipFile) throws IOException; - void saveAllToFolder(File folder); + void saveAllToFolder(File folder) throws IOException; + + void saveAllToStream(String metadata, ZipOutputStream stream) throws IOException; } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/preview/CustomPreview.java b/src/main/java/edu/kit/datamanager/ro_crate/preview/CustomPreview.java index 4e3bc3f4..5c8c9fa2 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/preview/CustomPreview.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/preview/CustomPreview.java @@ -1,68 +1,217 @@ package edu.kit.datamanager.ro_crate.preview; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.kit.datamanager.ro_crate.util.ZipUtil; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.TemplateExceptionHandler; import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import net.lingala.zip4j.ZipFile; import net.lingala.zip4j.exception.ZipException; -import net.lingala.zip4j.model.ZipParameters; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * This class represents the custom preview of a crate, - * which means html files created from outside sources. + * This class generates a custom preview without requiring external + * dependencies, i.e., rochtml. Therefore, the FreeMarker template located under + * resources/templates/custom_preview.ftl is used. * - * @author Nikola Tzotchev on 12.2.2022 г. - * @version 1 + * @author jejkal */ public class CustomPreview implements CratePreview { - private final File metadataHtml; - private final File otherFiles; - - public CustomPreview(File metadataHtml, File otherFiles) { - this.metadataHtml = metadataHtml; - this.otherFiles = otherFiles; - } - - public CustomPreview(File metadataHtml) { - this.metadataHtml = metadataHtml; - this.otherFiles = null; - } - - @Override - public void saveAllToZip(ZipFile zipFile) { - if (this.metadataHtml != null) { - try { - ZipParameters zipParameters = new ZipParameters(); - zipParameters.setFileNameInZip("ro-crate-preview.html"); - zipFile.addFile(this.metadataHtml, zipParameters); - } catch (ZipException e) { - System.err.println("Exception writing preview html to zip"); - } + private final static Logger logger = LoggerFactory.getLogger(CustomPreview.class); + + private Template template = null; + + public CustomPreview() { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_34); + cfg.setClassForTemplateLoading(CustomPreview.class, "/"); + cfg.setDefaultEncoding("UTF-8"); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + try { + template = cfg.getTemplate("templates/custom_preview.ftl"); + } catch (IOException ex) { + logger.error("Failed to read template for CustomPreview.", ex); + } + } + + private CustomPreviewModel mapFromJson(String metadata) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = (JsonNode) mapper.readValue(metadata, JsonNode.class); + JsonNode graph = root.get("@graph"); + CustomPreviewModel.ROCrate crate = new CustomPreviewModel.ROCrate(); + List datasets = new ArrayList<>(); + List files = new ArrayList<>(); + + if (graph.isArray()) { + + for (JsonNode node : graph) { + String id = node.path("@id").asText(); + List types = new LinkedList<>(); + if (node.path("@type").isArray()) { + + Collections.addAll(types, mapper.convertValue(node.path("@type"), String[].class)); + } else { + types.add(node.path("@type").asText()); + } + + if (types.contains("Dataset") && "./".equals(id)) { + crate.name = node.path("name").asText(); + crate.description = node.path("description").asText(null); + crate.type = "Dataset"; + crate.license = node.path("license").path("@id").asText(node.path("license").asText(null)); + crate.datePublished = node.path("datePublished").asText(null); + crate.hasPart = new ArrayList<>(); + + for (JsonNode part : node.path("hasPart")) { + CustomPreviewModel.Part p = new CustomPreviewModel.Part(); + String tmpId = part.path("@id").asText(part.asText()); + p.id = tmpId; + p.name = tmpId; // Name will be replaced later + crate.hasPart.add(p); + } + } else if (types.contains("Dataset")) { + CustomPreviewModel.Dataset dataset = new CustomPreviewModel.Dataset(); + dataset.id = id; + dataset.name = node.path("name").asText(); + dataset.description = node.path("description").asText(); + datasets.add(dataset); + } else if (types.contains("File")) { + CustomPreviewModel.File file = new CustomPreviewModel.File(); + file.id = id; + file.name = node.path("name").asText(null); + file.description = node.path("description").asText(null); + file.contentSize = node.path("contentSize").asText(null); + file.encodingFormat = node.path("encodingFormat").asText(null); + files.add(file); + } + } + } + + // Update Part names using dataset and file lists + if (crate.hasPart != null) { + for (CustomPreviewModel.Part part : crate.hasPart) { + for (CustomPreviewModel.Dataset dataset : datasets) { + if (dataset.id.equals(part.id) && dataset.name != null) { + part.name = dataset.name; + } + } + for (CustomPreviewModel.File file : files) { + if (file.id.equals(part.id) && file.name != null) { + part.name = file.name; + } + } + } + } + + CustomPreviewModel model = new CustomPreviewModel(); + model.crate = crate; + model.datasets = datasets; + model.files = files; + return model; + } + + @Override + public void saveAllToZip(ZipFile zipFile) throws IOException { + if (template == null) { + throw new IOException("Preview template did not load. Unable to proceed."); + } + if (zipFile == null) { + throw new IOException("Argument zipFile must not be null."); + } + try { + zipFile.extractFile("ro-crate-metadata.json", "temp"); + } catch (ZipException ex) { + throw new IOException("ro-crate-metadata.json not found in provided ZIP.", ex); + } + + String metadata = FileUtils.readFileToString(new File("temp/ro-crate-metadata.json"), "UTF-8"); + try { + Map dataModel = new HashMap<>(); + dataModel.put("crateModel", mapFromJson(metadata)); + + try (Writer out = new OutputStreamWriter(new FileOutputStream("temp/ro-crate-preview.html"))) { + template.process(dataModel, out); + out.flush(); + } + zipFile.addFile("temp/ro-crate-preview.html"); + } catch (TemplateException ex) { + throw new IOException("Failed to generate preview.", ex); + } finally { + try { + FileUtils.deleteDirectory(new File("temp")); + } catch (IOException ex) { + //ignore + } + } } - if (this.otherFiles != null) { - try { - zipFile.addFolder(this.otherFiles); - zipFile.renameFile(this.otherFiles.getName() + "/", "ro-crate-preview_files/"); - } catch (ZipException e) { - System.err.println("Exception writing preview files to zip"); - } + + @Override + public void saveAllToFolder(File folder) throws IOException { + if (template == null) { + throw new IOException("Preview template did not load. Unable to proceed."); + } + if (folder == null || !folder.exists()) { + throw new IOException("Preview target folder " + folder + " does not exist."); + } + String metadata = FileUtils.readFileToString(new File(folder, "ro-crate-metadata.json"), "UTF-8"); + try { + Map dataModel = new HashMap<>(); + dataModel.put("crateModel", mapFromJson(metadata)); + try (Writer out = new OutputStreamWriter(new FileOutputStream(new File(folder, "ro-crate-preview.html")))) { + template.process(dataModel, out); + out.flush(); + } + } catch (TemplateException ex) { + throw new IOException("Failed to generate preview.", ex); + } } - } - - @Override - public void saveAllToFolder(File folder) { - try { - if (this.metadataHtml != null) { - File fileInCrate = folder.toPath().resolve("ro-crate-preview.html").toFile(); - FileUtils.copyFile(this.metadataHtml, fileInCrate); - } - if (this.otherFiles != null) { - File folderName = folder.toPath().resolve("ro-crate-preview_files").toFile(); - FileUtils.copyDirectory(this.otherFiles, folderName); - } - } catch (IOException e) { - e.printStackTrace(); + + @Override + public void saveAllToStream(String metadata, ZipOutputStream stream) throws IOException { + if (template == null) { + throw new IOException("Preview template did not load. Unable to proceed."); + } + try { + //prepare metadata for template + Map dataModel = new HashMap<>(); + dataModel.put("crateModel", mapFromJson(metadata)); + + //prepare output folder and writer + FileUtils.forceMkdir(new File("temp")); + //load and process template + try (FileWriter writer = new FileWriter(new File("temp/ro-crate-preview.html"))) { + //load and process template + template.process(dataModel, writer); + writer.flush(); + } + + ZipUtil.addFileToZipStream(stream, new File("temp/ro-crate-preview.html"), "ro-crate-preview.html"); + } catch (TemplateException ex) { + throw new IOException("Failed to generate preview.", ex); + } finally { + try { + FileUtils.deleteDirectory(new File("temp")); + } catch (IOException ex) { + //ignore + } + } } - } + } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/preview/CustomPreviewModel.java b/src/main/java/edu/kit/datamanager/ro_crate/preview/CustomPreviewModel.java new file mode 100644 index 00000000..21dbd802 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/preview/CustomPreviewModel.java @@ -0,0 +1,126 @@ +package edu.kit.datamanager.ro_crate.preview; + +import java.util.List; + +/** + * + * @author jejkal + */ +public class CustomPreviewModel { + + protected ROCrate crate; + protected List datasets; + protected List files; + + public ROCrate getCrate() { + return crate; + } + + public List getDatasets() { + return datasets; + } + + public List getFiles() { + return files; + } + + public static class ROCrate { + + protected String name; + protected String description; + protected String type; + protected String license; + protected String datePublished; + protected List hasPart; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getType() { + return type; + } + + public String getLicense() { + return license; + } + + public String getDatePublished() { + return datePublished; + } + + public List getHasPart() { + return hasPart; + } + + } + + public static class Part { + + protected String id; + protected String name; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + } + + public static class Dataset { + + protected String id; + protected String name; + protected String description; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + } + + public static class File { + + protected String id; + protected String name; + protected String description; + protected String contentSize; + protected String encodingFormat; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getContentSize() { + return contentSize; + } + + public String getEncodingFormat() { + return encodingFormat; + } + + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/preview/PreviewGenerator.java b/src/main/java/edu/kit/datamanager/ro_crate/preview/PreviewGenerator.java index 55339ab4..60e8e19e 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/preview/PreviewGenerator.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/preview/PreviewGenerator.java @@ -1,57 +1,85 @@ package edu.kit.datamanager.ro_crate.preview; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** - * Class responsible for the generation of the human-readable representation of the metadata. - * For this to work the nodejs library rochtml has to be installed. - * This can be done using: - * npm install -g ro-crate-html-js - * or - * npm install ro-crate-html-js + * Class responsible for the generation of the human-readable representation of + * the metadata. For this to work the nodejs library rochtml has to be + * installed. This can be done using: npm install -g ro-crate-html-js or npm + * install ro-crate-html-js */ public class PreviewGenerator { - private static final String command = "rochtml"; - - /** - * The method that from the location of the crate generates the html file. - * - * @param location the location of the crate in the filesystem. - */ - public static void generatePreview(String location) { - ProcessBuilder builder = new ProcessBuilder(); - // this is the equivalent of "rochtml dir/ro-crate-metadata.json" - // check if we are running on windows or unix - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("win")) { - builder.command("cmd.exe", "/c", command + " " + location + "/ro-crate-metadata.json"); - - } else { - builder.command("sh", "-c", command + " " + location + "/ro-crate-metadata.json"); + private static Logger LOG = LoggerFactory.getLogger(PreviewGenerator.class); + private static final String command = "rochtml"; + + public static boolean isRochtmlAvailable() { + ProcessBuilder builder = new ProcessBuilder(); + // this is the equivalent of "rochtml dir/ro-crate-metadata.json" + // check if we are running on windows or unix + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + builder.command("cmd.exe", "/c", command); + + } else { + builder.command("sh", "-c", command); + } + Process process; + try { + process = builder.start(); + int exitVal = process.waitFor(); + return exitVal == 0; + } catch (InterruptedException | IOException ex) { + } + return false; } - Process process; - try { - process = builder.start(); - StringBuilder output = new StringBuilder(); - - BufferedReader reader = new BufferedReader( - new InputStreamReader(process.getInputStream())); - - String line; - while ((line = reader.readLine()) != null) { - output.append(line).append("\n"); - } - reader.close(); - int exitVal = process.waitFor(); - if (exitVal != 0) { - //abnormal... - throw new Exception("failed command"); - } - } catch (Exception e) { - e.printStackTrace(); + /** + * The method that from the location of the crate generates the html file. + * + * @param location the location of the crate in the filesystem. + */ + public static void generatePreview(String location) { + if (!isRochtmlAvailable()) { + LOG.error("rochtml is not available. Please install rochtml using npm install -g ro-crate-html-js"); + return; + } + ProcessBuilder builder = new ProcessBuilder(); + // this is the equivalent of "rochtml dir/ro-crate-metadata.json" + // check if we are running on windows or unix + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + builder.command("cmd.exe", "/c", command + " " + location + "/ro-crate-metadata.json"); + + } else { + builder.command("sh", "-c", command + " " + location + "/ro-crate-metadata.json"); + } + + Process process; + try { + process = builder.start(); + StringBuilder output = new StringBuilder(); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream())); + + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + reader.close(); + int exitVal = process.waitFor(); + if (exitVal != 0) { + //abnormal... + throw new Exception("failed command"); + } + } catch (Exception e) { + LOG.error("Error while generating preview: {}", e.getMessage()); + } } - } } 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 new file mode 100644 index 00000000..398615c1 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/preview/StaticPreview.java @@ -0,0 +1,73 @@ +package edu.kit.datamanager.ro_crate.preview; + +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; +import net.lingala.zip4j.model.ZipParameters; +import org.apache.commons.io.FileUtils; + +/** + * 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 + * file, folder, or stream. + * + * @author jejkal + */ +public class StaticPreview implements CratePreview { + + private final File metadataHtml; + private final File otherFiles; + + public StaticPreview(File metadataHtml, File otherFiles) { + this.metadataHtml = metadataHtml; + this.otherFiles = otherFiles; + } + + public StaticPreview(File metadataHtml) { + this.metadataHtml = metadataHtml; + this.otherFiles = null; + } + + @Override + public void saveAllToZip(ZipFile zipFile) throws IOException { + if (this.metadataHtml != null) { + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setFileNameInZip("ro-crate-preview.html"); + zipFile.addFile(this.metadataHtml, zipParameters); + } + + if (this.otherFiles != null) { + zipFile.addFolder(this.otherFiles); + zipFile.renameFile(this.otherFiles.getName() + "/", "ro-crate-preview_files/"); + } + } + + @Override + public void saveAllToFolder(File folder) throws IOException { + if (folder == null || !folder.exists()) { + throw new IOException("Preview target folder " + folder + " does not exist."); + } + + if (this.metadataHtml != null) { + File fileInCrate = folder.toPath().resolve("ro-crate-preview.html").toFile(); + FileUtils.copyFile(this.metadataHtml, fileInCrate); + } + if (this.otherFiles != null) { + File folderName = folder.toPath().resolve("ro-crate-preview_files").toFile(); + FileUtils.copyDirectory(this.otherFiles, folderName); + } + } + + @Override + public void saveAllToStream(String metadata, ZipOutputStream stream) throws IOException { + ZipUtil.addFileToZipStream(stream, this.metadataHtml, "ro-crate-preview.html"); + if (this.otherFiles != null) { + ZipUtil.addFolderToZipStream(stream, this.otherFiles, this.otherFiles.getName()); + } + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/CrateReader.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/CrateReader.java new file mode 100644 index 00000000..5acb575b --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/CrateReader.java @@ -0,0 +1,340 @@ +package edu.kit.datamanager.ro_crate.reader; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.context.CrateMetadataContext; +import edu.kit.datamanager.ro_crate.context.RoCrateMetadataContext; +import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; +import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity; +import edu.kit.datamanager.ro_crate.special.IdentifierUtils; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * This class allows reading crates from the outside into the library in order + * to inspect or modify it. + *

+ * The constructor takes a strategy to support different ways of importing the + * crates. (from zip, folder, etc.). + *

+ * The reader consideres "hasPart" and "isPartOf" properties and considers all + * entities (in-)directly connected to the root entity ("./") as DataEntities. + * + * @param the type of the location parameter + */ +public class CrateReader { + + private static final Logger logger = LoggerFactory.getLogger(CrateReader.class); + + /** + * This is a private inner class that shall not be exposed. **Do not make it + * public or protected.** It serves only the purpose of unsafe operations + * while reading a crate and may be specific to this implementation. + */ + private static class RoCrateUnsafe extends RoCrate { + + public void addDataEntityWithoutRootHasPart(DataEntity entity) { + this.metadataContext.checkEntity(entity); + this.roCratePayload.addDataEntity(entity); + } + } + + /** + * If the number of JSON entities in the crate is larger than this number, + * parallelization will be used. + */ + private static final int PARALLELIZATION_THRESHOLD = 100; + + private static final String FILE_PREVIEW_FILES = "ro-crate-preview_files"; + private static final String FILE_PREVIEW_HTML = "ro-crate-preview.html"; + private static final String FILE_METADATA_JSON = "ro-crate-metadata.json"; + + protected static final String SPECIFICATION_PREFIX = "https://w3id.org/ro/crate/"; + + protected static final String PROP_ABOUT = "about"; + protected static final String PROP_CONTEXT = "@context"; + protected static final String PROP_CONFORMS_TO = "conformsTo"; + protected static final String PROP_GRAPH = "@graph"; + protected static final String PROP_HAS_PART = "hasPart"; + protected static final String PROP_ID = "@id"; + + private final GenericReaderStrategy strategy; + + public CrateReader(GenericReaderStrategy strategy) { + this.strategy = strategy; + } + + /** + * This function will read the location (using one of the specified + * strategies) and then build the relation between the entities. + * + * @param location the location of the ro-crate to be read + * @return the read RO-crate + */ + public RoCrate readCrate(T location) { + // get the ro-crate-metadata.json + ObjectNode metadataJson = strategy.readMetadataJson(location); + // get the content of the crate + File files = strategy.readContent(location); + + // this set will contain the files that are associated with entities + HashSet usedFiles = new HashSet<>(); + usedFiles.add(files.toPath().resolve(FILE_METADATA_JSON).toFile().getPath()); + usedFiles.add(files.toPath().resolve(FILE_PREVIEW_HTML).toFile().getPath()); + usedFiles.add(files.toPath().resolve(FILE_PREVIEW_FILES).toFile().getPath()); + return rebuildCrate(metadataJson, files, usedFiles); + } + + private RoCrate rebuildCrate(ObjectNode metadataJson, File files, HashSet usedFiles) { + Objects.requireNonNull(metadataJson, + "metadataJson must not be null – did the strategy fail to locate 'ro-crate-metadata.json'?"); + Objects.requireNonNull(files, + "files directory must not be null – check GenericReaderStrategy.readContent()"); + JsonNode context = metadataJson.get(PROP_CONTEXT); + + CrateMetadataContext crateContext = new RoCrateMetadataContext(context); + RoCrateUnsafe crate = new RoCrateUnsafe(); + crate.setMetadataContext(crateContext); + JsonNode graph = metadataJson.get(PROP_GRAPH); + + if (graph.isArray()) { + moveRootEntitiesFromGraphToCrate(crate, (ArrayNode) graph); + RootDataEntity root = crate.getRootDataEntity(); + if (root != null) { + Set dataEntityIds = getDataEntityIds(root, graph); + for (JsonNode entityJson : graph) { + String eId = unpackId(entityJson); + if (dataEntityIds.contains(eId)) { + // data entity + DataEntity.DataEntityBuilder dataEntity = new DataEntity.DataEntityBuilder() + .setAll(entityJson.deepCopy()); + + // Handle data entities with corresponding file + checkFolderHasFile(entityJson.get(PROP_ID).asText(), files).ifPresent(file -> { + usedFiles.add(file.getPath()); + dataEntity.setLocationWithExceptions(file.toPath()) + .setId(file.getName()); + }); + + crate.addDataEntityWithoutRootHasPart(dataEntity.build()); + } else { + // contextual entity + crate.addContextualEntity( + new ContextualEntity.ContextualEntityBuilder() + .setAll(entityJson.deepCopy()) + .build()); + } + } + } + } + + Collection untrackedFiles = Arrays.stream( + Optional.ofNullable(files.listFiles()).orElse(new File[0])) + .filter(f -> !usedFiles.contains(f.getPath())) + .collect(Collectors.toSet()); + + crate.setUntrackedFiles(untrackedFiles); + Validator defaultValidation = new Validator(new JsonSchemaValidation()); + defaultValidation.validate(crate); + return crate; + } + + /** + * Extracts graph connections from top to bottom. + *

+ * Example: (connections.get(parent) -> children) + * + * @param graph the ArrayNode with all Entities. + * @return the graph connections. + */ + protected Map> makeEntityGraph(JsonNode graph) { + Map> connections = new HashMap<>(); + + Map idToNodes = new HashMap<>(); + StreamSupport.stream(graph.spliterator(), false) + .forEach(jsonNode -> idToNodes.put(unpackId(jsonNode), jsonNode)); + + for (JsonNode entityNode : graph) { + String currentId = unpackId(entityNode); + StreamSupport.stream(entityNode.path("hasPart").spliterator(), false) + .map(this::unpackId) + .map(s -> idToNodes.getOrDefault(s, null)) + .filter(Objects::nonNull) + .forEach(child -> connections.computeIfAbsent(currentId, key -> new HashSet<>()) + .add(unpackId(child))); + StreamSupport.stream(entityNode.path("isPartOf").spliterator(), false) + .map(this::unpackId) + .map(s -> idToNodes.getOrDefault(s, null)) + .filter(Objects::nonNull) + .forEach(parent -> connections.computeIfAbsent(unpackId(parent), key -> new HashSet<>()) + .add(currentId)); + } + return connections; + } + + protected Set getDataEntityIds(RootDataEntity root, JsonNode graph) { + if (root == null) { + return Set.of(); + } + Map> network = makeEntityGraph(graph); + Set directDataEntities = new HashSet<>(root.hasPart); + + Stack processingQueue = new Stack<>(); + processingQueue.addAll(directDataEntities); + Set result = new HashSet<>(); + + while (!processingQueue.empty()) { + String currentId = processingQueue.pop(); + result.add(currentId); + network.getOrDefault(currentId, new HashSet<>()).stream() + .filter(subId -> !result.contains(subId)) // avoid loops! + .forEach(subId -> { + result.add(subId); + processingQueue.add(subId); + }); + } + return result; + } + + protected String unpackId(JsonNode node) { + if (node.isTextual()) { + return node.asText(); + } else /*if (node.isObject())*/ { + return node.path(PROP_ID).asText(); + } + } + + protected Optional checkFolderHasFile(String filepathOrId, File folder) { + if (IdentifierUtils.isUrl(filepathOrId)) { + return Optional.empty(); + } + return IdentifierUtils.decode(filepathOrId) + .map(decoded -> folder.toPath().resolve(decoded).normalize()) + // defence-in-depth: ensure we are still inside the crate folder + .filter(resolved -> resolved.startsWith(folder.toPath())) + .map(Path::toFile) + .filter(File::exists); + } + + /** + * Moves the descriptor and the root entity from the graph to the crate. + *

+ * Extracts the root data entity and the Metadata File Descriptor from the + * graph and inserts them into the crate object. It also deletes it from the + * graph. We will need the root dataset to distinguish between data entities + * and contextual entities. + * + * @param crate the crate, which will receive the entities, if available in + * the graph. + * @param graph the graph of the Metadata JSON file, where the entities are + * extracted and removed from. + */ + protected void moveRootEntitiesFromGraphToCrate(RoCrate crate, ArrayNode graph) { + Optional maybeDescriptor = getMetadataDescriptor(graph); + + maybeDescriptor.ifPresent(descriptor -> { + setCrateDescriptor(crate, descriptor); + JsonUtilFunctions.removeJsonNodeFromArrayNode(graph, descriptor); + + Optional maybeRoot = extractRoot(graph, descriptor); + + maybeRoot.ifPresent(root -> { + Set hasPartIds = extractHasPartIds(root); + + crate.setRootDataEntity( + new RootDataEntity.RootDataEntityBuilder() + .setAll(root.deepCopy()) + .setHasPart(hasPartIds) + .build()); + + JsonUtilFunctions.removeJsonNodeFromArrayNode(graph, root); + }); + }); + } + + /** + * Find the metadata descriptor. + *

+ * Currently prefers algorithm of version 1.1 over the one of 1.2-DRAFT. + * + * @param graph the graph to search the descriptor in. + * @return the metadata descriptor of the crate. + */ + protected Optional getMetadataDescriptor(ArrayNode graph) { + boolean isParallel = graph.size() > PARALLELIZATION_THRESHOLD; + // use the algorithm described here: + // https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity + Optional maybeDescriptor = StreamSupport.stream(graph.spliterator(), isParallel) + // "2. if the conformsTo property is a URI that starts with + // https://w3id.org/ro/crate/" + .filter(node -> node.path(PROP_CONFORMS_TO).path(PROP_ID).asText().startsWith(SPECIFICATION_PREFIX)) + // "3. from this entity’s about object keep the @id URI as variable root" + .filter(node -> node.path(PROP_ABOUT).path(PROP_ID).isTextual()) + // There should be only one descriptor. If multiple exist, we take the first + // one. + .findFirst(); + return maybeDescriptor.or(() + -> // from https://www.researchobject.org/ro-crate/1.2-DRAFT/root-data-entity.html#finding-the-root-data-entity + StreamSupport.stream(graph.spliterator(), isParallel) + .filter(node -> node.path(PROP_ID).asText().equals(FILE_METADATA_JSON)) + .findFirst() + ); + } + + /** + * Extracts the root entity from the graph, using the information from the + * descriptor. + *

+ * Basically implements step 5 of the algorithm described here: + * + * https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity + * + * + * @param graph the graph from the metadata JSON-LD file + * @param descriptor the RO-Crate descriptor + * @return the root entity, if found + */ + private Optional extractRoot(ArrayNode graph, JsonNode descriptor) { + String rootId = descriptor.get(PROP_ABOUT).get(PROP_ID).asText(); + boolean isParallel = graph.size() > PARALLELIZATION_THRESHOLD; + return StreamSupport.stream(graph.spliterator(), isParallel) + // root is an object (filter + conversion) + .filter(JsonNode::isObject) + .map(JsonNode::deepCopy) + // "5. if the entity has an @id URI that matches root return it" + .filter(node -> node.path(PROP_ID).asText().equals(rootId)) + .findFirst(); + } + + private Set extractHasPartIds(ObjectNode root) { + JsonNode hasPartNode = root.path(PROP_HAS_PART); + boolean isParallel = hasPartNode.isArray() && hasPartNode.size() > PARALLELIZATION_THRESHOLD; + Set hasPartIds = StreamSupport.stream(hasPartNode.spliterator(), isParallel) + .map(hasPart -> hasPart.path(PROP_ID).asText()) + .filter(text -> !text.isBlank()) + .collect(Collectors.toSet()); + if (hasPartIds.isEmpty() && hasPartNode.path(PROP_ID).isTextual()) { + hasPartIds.add(hasPartNode.path(PROP_ID).asText()); + } + return hasPartIds; + } + + private void setCrateDescriptor(RoCrate crate, JsonNode descriptor) { + ContextualEntity descriptorEntity = new ContextualEntity.ContextualEntityBuilder() + .setAll(descriptor.deepCopy()) + .build(); + crate.setJsonDescriptor(descriptorEntity); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/FolderReader.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/FolderReader.java index faaede49..f0436e27 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/reader/FolderReader.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/FolderReader.java @@ -1,37 +1,12 @@ package edu.kit.datamanager.ro_crate.reader; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; - /** * A class for reading a crate from a folder. * * @author Nikola Tzotchev on 9.2.2022 г. * @version 1 + * + * @deprecated Use {@link FolderStrategy} instead. */ -public class FolderReader implements ReaderStrategy { - - @Override - public ObjectNode readMetadataJson(String location) { - Path metadata = new File(location).toPath().resolve("ro-crate-metadata.json"); - ObjectMapper objectMapper = MyObjectMapper.getMapper(); - ObjectNode objectNode = objectMapper.createObjectNode(); - try { - objectNode = objectMapper.readTree(metadata.toFile()).deepCopy(); - } catch (IOException e) { - e.printStackTrace(); - } - return objectNode; - } - - @Override - public File readContent(String location) { - return new File(location); - } -} +@Deprecated(since = "2.1.0", forRemoval = true) +public class FolderReader extends FolderStrategy {} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/FolderStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/FolderStrategy.java new file mode 100644 index 00000000..f6e235ed --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/FolderStrategy.java @@ -0,0 +1,36 @@ +package edu.kit.datamanager.ro_crate.reader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +/** + * A class for reading a crate from a folder. + * + * @author Nikola Tzotchev on 9.2.2022 г. + * @version 1 + */ +public class FolderStrategy implements GenericReaderStrategy { + + @Override + public ObjectNode readMetadataJson(String location) { + Path metadata = new File(location).toPath().resolve("ro-crate-metadata.json"); + ObjectMapper objectMapper = MyObjectMapper.getMapper(); + ObjectNode objectNode = objectMapper.createObjectNode(); + try { + objectNode = objectMapper.readTree(metadata.toFile()).deepCopy(); + } catch (IOException e) { + e.printStackTrace(); + } + return objectNode; + } + + @Override + public File readContent(String location) { + return new File(location); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/GenericReaderStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/GenericReaderStrategy.java new file mode 100644 index 00000000..c3539b17 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/GenericReaderStrategy.java @@ -0,0 +1,28 @@ +package edu.kit.datamanager.ro_crate.reader; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.File; + +/** + * Generic interface for the strategy of the reader class. + * This allows for flexible input types when implementing different reading strategies. + * + * @param the type of the location parameter + */ +public interface GenericReaderStrategy { + /** + * Read the metadata.json file from the given location. + * + * @param location the location to read from + * @return the parsed metadata.json as ObjectNode + */ + ObjectNode readMetadataJson(T location); + + /** + * Read the content from the given location. + * + * @param location the location to read from + * @return the content as a File + */ + File readContent(T location); +} \ No newline at end of file diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/ReaderStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/ReaderStrategy.java index 506dfd4f..2029fe9c 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/reader/ReaderStrategy.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/ReaderStrategy.java @@ -1,18 +1,14 @@ package edu.kit.datamanager.ro_crate.reader; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.io.File; - /** - * Interface for the strategy fo the reader class. + * Interface for the strategy for the reader class. * This should be implemented if additional strategies are to be build. * (e.g., reading from a gzip) * * @author Nikola Tzotchev on 9.2.2022 г. * @version 1 + * + * @deprecated Use {@link GenericReaderStrategy} instead. */ -public interface ReaderStrategy { - ObjectNode readMetadataJson(String location); - - File readContent(String location); -} +@Deprecated(since = "2.1.0", forRemoval = true) +public interface ReaderStrategy extends GenericReaderStrategy {} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/Readers.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/Readers.java new file mode 100644 index 00000000..49e09cb1 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/Readers.java @@ -0,0 +1,77 @@ +package edu.kit.datamanager.ro_crate.reader; + +import java.io.InputStream; +import java.nio.file.Path; + +/** + * Factory for creating common RO-Crate reader instances. + * Provides convenient static methods to instantiate readers with pre-configured strategies. + */ +public class Readers { + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private Readers() {} + + /** + * Creates a reader that reads from ZIP files using input streams. + * + * @return A reader configured for ZIP files + * + * @see ZipStreamStrategy#ZipStreamStrategy() + */ + public static CrateReader newZipStreamReader() { + return new CrateReader<>(new ZipStreamStrategy()); + } + + /** + * Creates a reader that reads from ZIP files using input streams, + * extracting to a custom temporary location. + * + * @param extractPath Path where ZIP contents should be extracted + * @param useUuidSubfolder Whether to create a UUID subfolder under extractPath + * @return A reader configured for ZIP files with custom extraction + * + * @see ZipStreamStrategy#ZipStreamStrategy(Path, boolean) + */ + public static CrateReader newZipStreamReader(Path extractPath, boolean useUuidSubfolder) { + return new CrateReader<>(new ZipStreamStrategy(extractPath, useUuidSubfolder)); + } + + /** + * Creates a reader that reads from a folder using a string path. + * + * @return A reader configured for folders + * + * @see FolderStrategy + */ + public static CrateReader newFolderReader() { + return new CrateReader<>(new FolderStrategy()); + } + + /** + * Creates a reader that reads from a ZIP file using a string path. + * + * @return A reader configured for ZIP files + * + * @see ZipStrategy#ZipStrategy() + */ + public static CrateReader newZipPathReader() { + return new CrateReader<>(new ZipStrategy()); + } + + /** + * Creates a reader that reads from a ZIP file using a string path, + * extracting to a custom temporary location. + * + * @param extractPath Path where ZIP contents should be extracted + * @param useUuidSubfolder Whether to create a UUID subfolder under extractPath + * @return A reader configured for ZIP files with custom extraction + * + * @see ZipStrategy#ZipStrategy(Path, boolean) + */ + public static CrateReader newZipPathReader(Path extractPath, boolean useUuidSubfolder) { + return new CrateReader<>(new ZipStrategy(extractPath, useUuidSubfolder)); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/RoCrateReader.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/RoCrateReader.java index 50ce250a..d11177c1 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/reader/RoCrateReader.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/RoCrateReader.java @@ -1,323 +1,20 @@ package edu.kit.datamanager.ro_crate.reader; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import edu.kit.datamanager.ro_crate.RoCrate; -import edu.kit.datamanager.ro_crate.context.CrateMetadataContext; -import edu.kit.datamanager.ro_crate.context.RoCrateMetadataContext; -import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity; -import edu.kit.datamanager.ro_crate.entities.data.DataEntity; -import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity; -import edu.kit.datamanager.ro_crate.special.IdentifierUtils; -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 java.io.File; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - /** * This class allows reading crates from the outside into the library in order * to inspect or modify it. *

- * The constructor takes a strategy to support different ways of importing the crates. - * (from zip, folder, etc.). + * The constructor takes a strategy to support different ways of importing the + * crates. (from zip, folder, etc.). *

* The reader consideres "hasPart" and "isPartOf" properties and considers all * entities (in-)directly connected to the root entity ("./") as DataEntities. + * + * @deprecated Use {@link CrateReader} instead. */ -public class RoCrateReader { - - /** - * This is a private inner class that shall not be exposed. - * **Do not make it public or protected.** It serves only the - * purpose of unsafe operations while reading a crate and - * may be specific to this implementation. - */ - private static class RoCrateUnsafe extends RoCrate { - public void addDataEntityWithoutRootHasPart(DataEntity entity) { - this.metadataContext.checkEntity(entity); - this.roCratePayload.addDataEntity(entity); +@Deprecated(since = "2.1.0", forRemoval = true) +public class RoCrateReader extends CrateReader { + public RoCrateReader(GenericReaderStrategy reader) { + super(reader); } - } - - /** - * If the number of JSON entities in the crate is larger than this number, - * parallelization will be used. - */ - private static final int PARALLELIZATION_THRESHOLD = 100; - - private static final String FILE_PREVIEW_FILES = "ro-crate-preview_files"; - private static final String FILE_PREVIEW_HTML = "ro-crate-preview.html"; - private static final String FILE_METADATA_JSON = "ro-crate-metadata.json"; - - protected static final String SPECIFICATION_PREFIX = "https://w3id.org/ro/crate/"; - - protected static final String PROP_ABOUT = "about"; - protected static final String PROP_CONTEXT = "@context"; - protected static final String PROP_CONFORMS_TO = "conformsTo"; - protected static final String PROP_GRAPH = "@graph"; - protected static final String PROP_HAS_PART = "hasPart"; - protected static final String PROP_ID = "@id"; - - private final ReaderStrategy reader; - - public RoCrateReader(ReaderStrategy reader) { - this.reader = reader; - } - - /** - * This function will read the location (using one of the specified strategies) and then - * build the relation between the entities. - * - * @param location the location of the ro-crate to be read - * @return the read RO-crate - */ - public RoCrate readCrate(String location) { - // get the ro-crate-medata.json - ObjectNode metadataJson = reader.readMetadataJson(location); - // get the content of the crate - File files = reader.readContent(location); - - // this set will contain the files that are associated with entities - HashSet usedFiles = new HashSet<>(); - usedFiles.add(new File(location).toPath().resolve(FILE_METADATA_JSON).toFile().getPath()); - usedFiles.add(new File(location).toPath().resolve(FILE_PREVIEW_HTML).toFile().getPath()); - usedFiles.add(new File(location).toPath().resolve(FILE_PREVIEW_FILES).toFile().getPath()); - - JsonNode context = metadataJson.get(PROP_CONTEXT); - - CrateMetadataContext crateContext = new RoCrateMetadataContext(context); - RoCrateUnsafe crate = new RoCrateUnsafe(); - crate.setMetadataContext(crateContext); - JsonNode graph = metadataJson.get(PROP_GRAPH); - - if (graph.isArray()) { - moveRootEntitiesFromGraphToCrate(crate, (ArrayNode) graph); - RootDataEntity root = crate.getRootDataEntity(); - if (root != null) { - Set dataEntityIds = getDataEntityIds(root, graph); - for (JsonNode entityJson : graph) { - String eId = unpackId(entityJson); - if (dataEntityIds.contains(eId)) { - // data entity - DataEntity.DataEntityBuilder dataEntity = new DataEntity.DataEntityBuilder() - .setAll(entityJson.deepCopy()); - - // Handle data entities with corresponding file - checkFolderHasFile(entityJson.get(PROP_ID).asText(), files).ifPresent(file -> { - usedFiles.add(file.getPath()); - dataEntity.setLocationWithExceptions(file.toPath()) - .setId(file.getName()); - }); - - crate.addDataEntityWithoutRootHasPart(dataEntity.build()); - } else { - // contextual entity - crate.addContextualEntity( - new ContextualEntity.ContextualEntityBuilder() - .setAll(entityJson.deepCopy()) - .build()); - } - } - } - } - - Collection untrackedFiles = Arrays.stream( - Optional.ofNullable(files.listFiles()).orElse(new File[0])) - .filter(f -> !usedFiles.contains(f.getPath())) - .collect(Collectors.toSet()); - - crate.setUntrackedFiles(untrackedFiles); - Validator defaultValidation = new Validator(new JsonSchemaValidation()); - defaultValidation.validate(crate); - return crate; - } - - /** - * Extracts graph connections from top to bottom. - *

- * Example: (connections.get(parent) -> children) - * - * @param graph the ArrayNode with all Entities. - * @return the graph connections. - */ - protected Map> makeEntityGraph(JsonNode graph) { - Map> connections = new HashMap<>(); - - Map idToNodes = new HashMap<>(); - StreamSupport.stream(graph.spliterator(), false) - .forEach(jsonNode -> idToNodes.put(unpackId(jsonNode), jsonNode)); - - for (JsonNode entityNode : graph) { - String currentId = unpackId(entityNode); - StreamSupport.stream(entityNode.path("hasPart").spliterator(), false) - .map(this::unpackId) - .map(s -> idToNodes.getOrDefault(s, null)) - .filter(Objects::nonNull) - .forEach(child -> connections.computeIfAbsent(currentId, key -> new HashSet<>()) - .add(unpackId(child))); - StreamSupport.stream(entityNode.path("isPartOf").spliterator(), false) - .map(this::unpackId) - .map(s -> idToNodes.getOrDefault(s, null)) - .filter(Objects::nonNull) - .forEach(parent -> connections.computeIfAbsent(unpackId(parent), key -> new HashSet<>()) - .add(currentId)); - } - return connections; - } - - protected Set getDataEntityIds(RootDataEntity root, JsonNode graph) { - if (root == null) { return Set.of(); } - Map> network = makeEntityGraph(graph); - Set directDataEntities = new HashSet<>(root.hasPart); - - Stack processingQueue = new Stack<>(); - processingQueue.addAll(directDataEntities); - Set result = new HashSet<>(); - - while (!processingQueue.empty()) { - String currentId = processingQueue.pop(); - result.add(currentId); - network.getOrDefault(currentId, new HashSet<>()).stream() - .filter(subId -> !result.contains(subId)) // avoid loops! - .forEach(subId -> { - result.add(subId); - processingQueue.add(subId); - }); - } - return result; - } - - protected String unpackId(JsonNode node) { - if (node.isTextual()) { - return node.asText(); - } else /*if (node.isObject())*/ { - return node.path(PROP_ID).asText(); - } - } - - protected Optional checkFolderHasFile(String filepathOrId, File folder) { - if (IdentifierUtils.isUrl(filepathOrId)) { return Optional.empty(); } - return IdentifierUtils.decode(filepathOrId) - .map(decoded -> folder.toPath().resolve(decoded).toFile()) - .filter(File::exists); - } - - /** - * Moves the descriptor and the root entity from the graph to the crate. - *

- * Extracts the root data entity and the Metadata File Descriptor from the graph - * and inserts them into the crate object. It also deletes it from the graph. - * We will need the root dataset to distinguish between data entities and - * contextual entities. - * - * @param crate the crate, which will receive the entities, if available in the - * graph. - * @param graph the graph of the Metadata JSON file, where the entities are - * extracted and removed from. - */ - protected void moveRootEntitiesFromGraphToCrate(RoCrate crate, ArrayNode graph) { - Optional maybeDescriptor = getMetadataDescriptor(graph); - - maybeDescriptor.ifPresent(descriptor -> { - setCrateDescriptor(crate, descriptor); - JsonUtilFunctions.removeJsonNodeFromArrayNode(graph, descriptor); - - Optional maybeRoot = extractRoot(graph, descriptor); - - maybeRoot.ifPresent(root -> { - Set hasPartIds = extractHasPartIds(root); - - crate.setRootDataEntity( - new RootDataEntity.RootDataEntityBuilder() - .setAll(root.deepCopy()) - .setHasPart(hasPartIds) - .build()); - - JsonUtilFunctions.removeJsonNodeFromArrayNode(graph, root); - }); - }); - } - - /** - * Find the metadata descriptor. - *

- * Currently prefers algorithm of version 1.1 over the one of 1.2-DRAFT. - * - * @param graph the graph to search the descriptor in. - * @return the metadata descriptor of the crate. - */ - protected Optional getMetadataDescriptor(ArrayNode graph) { - boolean isParallel = graph.size() > PARALLELIZATION_THRESHOLD; - // use the algorithm described here: - // https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity - Optional maybeDescriptor = StreamSupport.stream(graph.spliterator(), isParallel) - // "2. if the conformsTo property is a URI that starts with - // https://w3id.org/ro/crate/" - .filter(node -> node.path(PROP_CONFORMS_TO).path(PROP_ID).asText().startsWith(SPECIFICATION_PREFIX)) - // "3. from this entity’s about object keep the @id URI as variable root" - .filter(node -> node.path(PROP_ABOUT).path(PROP_ID).isTextual()) - // There should be only one descriptor. If multiple exist, we take the first - // one. - .findFirst(); - return maybeDescriptor.or( () -> - // from https://www.researchobject.org/ro-crate/1.2-DRAFT/root-data-entity.html#finding-the-root-data-entity - StreamSupport.stream(graph.spliterator(), isParallel) - .filter(node -> node.path(PROP_ID).asText().equals(FILE_METADATA_JSON)) - .findFirst() - ); - } - - /** - * Extracts the root entity from the graph, using the information from the - * descriptor. - *

- * Basically implements step 5 of the algorithm described here: - * - * https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity - * - * - * @param graph the graph from the metadata JSON-LD file - * @param descriptor the RO-Crate descriptor - * @return the root entity, if found - */ - private Optional extractRoot(ArrayNode graph, JsonNode descriptor) { - String rootId = descriptor.get(PROP_ABOUT).get(PROP_ID).asText(); - boolean isParallel = graph.size() > PARALLELIZATION_THRESHOLD; - return StreamSupport.stream(graph.spliterator(), isParallel) - // root is an object (filter + conversion) - .filter(JsonNode::isObject) - .map(JsonNode::deepCopy) - // "5. if the entity has an @id URI that matches root return it" - .filter(node -> node.path(PROP_ID).asText().equals(rootId)) - .findFirst(); - } - - private Set extractHasPartIds(ObjectNode root) { - JsonNode hasPartNode = root.path(PROP_HAS_PART); - boolean isParallel = hasPartNode.isArray() && hasPartNode.size() > PARALLELIZATION_THRESHOLD; - Set hasPartIds = StreamSupport.stream(hasPartNode.spliterator(), isParallel) - .map(hasPart -> hasPart.path(PROP_ID).asText()) - .filter(text -> !text.isBlank()) - .collect(Collectors.toSet()); - if (hasPartIds.isEmpty() && hasPartNode.path(PROP_ID).isTextual()) { - hasPartIds.add(hasPartNode.path(PROP_ID).asText()); - } - return hasPartIds; - } - - private void setCrateDescriptor(RoCrate crate, JsonNode descriptor) { - ContextualEntity descriptorEntity = new ContextualEntity.ContextualEntityBuilder() - .setAll(descriptor.deepCopy()) - .build(); - crate.setJsonDescriptor(descriptorEntity); - } } - diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipReader.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipReader.java index da60798b..ad9bbb01 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipReader.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipReader.java @@ -1,18 +1,7 @@ package edu.kit.datamanager.ro_crate.reader; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; - -import java.io.File; -import java.io.IOException; -import java.util.UUID; import java.nio.file.Path; -import net.lingala.zip4j.ZipFile; -import org.apache.commons.io.FileUtils; - /** * A ReaderStrategy implementation which reads from ZipFiles. *

@@ -28,17 +17,18 @@ * folder after extraction. Use RoCrateWriter to export it so some * persistent location and possibly read it from there, if required. Or use * the ZipWriter to write it back to its source. + * + * @deprecated Use {@link ZipStrategy} instead. */ -public class ZipReader implements ReaderStrategy { - - protected final String ID = UUID.randomUUID().toString(); - protected Path temporaryFolder = Path.of(String.format("./.tmp/ro-crate-java/zipReader/%s/", ID)); - protected boolean isExtracted = false; +@Deprecated(since = "2.1.0", forRemoval = true) +public class ZipReader extends ZipStrategy { /** * Crates a ZipReader with the default configuration as described in the class documentation. */ - public ZipReader() {} + public ZipReader() { + super(); + } /** * Creates a ZipReader which will extract the contents temporary @@ -52,77 +42,6 @@ public ZipReader() {} * will have UUIDs as their names. */ public ZipReader(Path folderPath, boolean shallAddUuidSubfolder) { - if (shallAddUuidSubfolder) { - this.temporaryFolder = folderPath.resolve(ID); - } else { - this.temporaryFolder = folderPath; - } - } - - /** - * @return the identifier which may be used as the name for a subfolder in the temporary directory. - */ - public String getID() { - return ID; - } - - /** - * @return the folder (considered temporary) where the zipped crate will be or has been extracted to. - */ - public Path getTemporaryFolder() { - return temporaryFolder; - } - - /** - * @return whether the crate has already been extracted into the temporary folder. - */ - public boolean isExtracted() { - return isExtracted; - } - - private void readCrate(String location) { - try { - File folder = temporaryFolder.toFile(); - // ensure the directory is clean - if (folder.isDirectory()) { - FileUtils.cleanDirectory(folder); - } else if (folder.isFile()) { - FileUtils.delete(folder); - } - // extract - try (ZipFile zf = new ZipFile(location)) { - zf.extractAll(temporaryFolder.toAbsolutePath().toString()); - this.isExtracted = true; - } - // register deletion on exit - FileUtils.forceDeleteOnExit(folder); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public ObjectNode readMetadataJson(String location) { - if (!isExtracted) { - this.readCrate(location); - } - - ObjectMapper objectMapper = MyObjectMapper.getMapper(); - File jsonMetadata = temporaryFolder.resolve("ro-crate-metadata.json").toFile(); - - try { - return objectMapper.readTree(jsonMetadata).deepCopy(); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - @Override - public File readContent(String location) { - if (!isExtracted) { - this.readCrate(location); - } - return temporaryFolder.toFile(); + super(folderPath, shallAddUuidSubfolder); } } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStrategy.java new file mode 100644 index 00000000..0d6381a6 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStrategy.java @@ -0,0 +1,126 @@ +package edu.kit.datamanager.ro_crate.reader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; +import net.lingala.zip4j.ZipFile; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.UUID; + +/** + * A ReaderStrategy implementation which reads from ZipFiles. + *

+ * May be used as a dependency for CrateReader. It will unzip + * the ZipFile in a path relative to the directory this application runs in. + * By default, it will be `./.tmp/ro-crate-java/zipReader/$UUID/`. + *

+ * NOTE: The resulting crate may refer to these temporary files. Therefore, + * these files are only being deleted before the JVM exits. If you need to free + * space because your application is long-running or creates a lot of + * crates, you may use the getters to retrieve information which will help + * you to clean up manually. Keep in mind that crates may refer to this + * folder after extraction. Use RoCrateWriter to export it so some + * persistent location and possibly read it from there, if required. Or use + * the ZipWriter to write it back to its source. + */ +public class ZipStrategy implements GenericReaderStrategy { + + protected final String ID = UUID.randomUUID().toString(); + protected Path temporaryFolder = Path.of(String.format("./.tmp/ro-crate-java/zipReader/%s/", ID)); + protected boolean isExtracted = false; + + /** + * Crates a ZipReader with the default configuration as described in the class documentation. + */ + public ZipStrategy() {} + + /** + * Creates a ZipReader which will extract the contents temporary + * to the given location instead of the default location. + * + * @param folderPath the custom directory to extract + * content to for temporary access. + * @param shallAddUuidSubfolder if true, the reader will extract + * into subdirectories of the given + * directory. These subdirectories + * will have UUIDs as their names. + */ + public ZipStrategy(Path folderPath, boolean shallAddUuidSubfolder) { + if (shallAddUuidSubfolder) { + this.temporaryFolder = folderPath.resolve(ID); + } else { + this.temporaryFolder = folderPath; + } + } + + /** + * @return the identifier which may be used as the name for a subfolder in the temporary directory. + */ + public String getID() { + return ID; + } + + /** + * @return the folder (considered temporary) where the zipped crate will be or has been extracted to. + */ + public Path getTemporaryFolder() { + return temporaryFolder; + } + + /** + * @return whether the crate has already been extracted into the temporary folder. + */ + public boolean isExtracted() { + return isExtracted; + } + + private void readCrate(String location) { + try { + File folder = temporaryFolder.toFile(); + // ensure the directory is clean + if (folder.isDirectory()) { + FileUtils.cleanDirectory(folder); + } else if (folder.isFile()) { + FileUtils.delete(folder); + } + // extract + try (ZipFile zf = new ZipFile(location)) { + zf.extractAll(temporaryFolder.toAbsolutePath().toString()); + this.isExtracted = true; + } + // register deletion on exit + FileUtils.forceDeleteOnExit(folder); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public ObjectNode readMetadataJson(String location) { + if (!isExtracted) { + this.readCrate(location); + } + + ObjectMapper objectMapper = MyObjectMapper.getMapper(); + File jsonMetadata = temporaryFolder.resolve("ro-crate-metadata.json").toFile(); + + try { + return objectMapper.readTree(jsonMetadata).deepCopy(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public File readContent(String location) { + if (!isExtracted) { + this.readCrate(location); + } + return temporaryFolder.toFile(); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamStrategy.java new file mode 100644 index 00000000..cb4f53af --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamStrategy.java @@ -0,0 +1,150 @@ +package edu.kit.datamanager.ro_crate.reader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.UUID; +import net.lingala.zip4j.io.inputstream.ZipInputStream; +import net.lingala.zip4j.model.LocalFileHeader; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A ZIP file reader implementation of the StreamReaderStrategy interface. + * This class handles reading and extraction of RO-Crate content from ZIP archives + * into a temporary directory structure, which allows for accessing the contained files. + * + * @author jejkal + */ +public class ZipStreamStrategy implements GenericReaderStrategy { + + private static final Logger logger = LoggerFactory.getLogger(ZipStreamStrategy.class); + protected final String ID = UUID.randomUUID().toString(); + protected Path temporaryFolder = Path.of(String.format("./.tmp/ro-crate-java/zipStreamReader/%s/", ID)); + protected boolean isExtracted = false; + + /** + * Crates a ZipStreamReader with the default configuration as described in + * the class documentation. + */ + public ZipStreamStrategy() { + } + + /** + * Creates a ZipStreamReader which will extract the contents temporary to + * the given location instead of the default location. + * + * @param folderPath the custom directory to extract content to for + * temporary access. + * @param shallAddUuidSubfolder if true, the reader will extract into + * subdirectories of the given directory. These subdirectories will have + * UUIDs as their names. + */ + public ZipStreamStrategy(Path folderPath, boolean shallAddUuidSubfolder) { + if (shallAddUuidSubfolder) { + this.temporaryFolder = folderPath.resolve(ID); + } else { + this.temporaryFolder = folderPath; + } + } + + /** + * @return the identifier which may be used as the name for a subfolder in + * the temporary directory. + */ + public String getID() { + return ID; + } + + /** + * @return the folder (considered temporary) where the zipped crate will be + * or has been extracted to. + */ + public Path getTemporaryFolder() { + return temporaryFolder; + } + + /** + * @return whether the crate has already been extracted into the temporary + * folder. + */ + public boolean isExtracted() { + return isExtracted; + } + + /**Read the crate metadata and content from the provided input stream. + * + * @param stream The input stream. + */ + private void readCrate(InputStream stream) { + try { + File folder = temporaryFolder.toFile(); + // ensure the directory is clean + if (folder.exists()) { + if (folder.isDirectory()) { + FileUtils.cleanDirectory(folder); + } else if (folder.isFile()) { + FileUtils.delete(folder); + } + } else { + FileUtils.forceMkdir(folder); + } + + LocalFileHeader localFileHeader; + int readLen; + byte[] readBuffer = new byte[4096]; + + try (ZipInputStream zipInputStream = new ZipInputStream(stream)) { + while ((localFileHeader = zipInputStream.getNextEntry()) != null) { + String fileName = localFileHeader.getFileName(); + File extractedFile = new File(folder, fileName).getCanonicalFile(); + if (!extractedFile.toPath().startsWith(folder.getCanonicalPath())) { + throw new IOException("Entry is outside of target directory: " + fileName); + } + try (OutputStream outputStream = new FileOutputStream(extractedFile)) { + while ((readLen = zipInputStream.read(readBuffer)) != -1) { + outputStream.write(readBuffer, 0, readLen); + } + } + } + } + this.isExtracted = true; + // register deletion on exit + FileUtils.forceDeleteOnExit(folder); + } catch (IOException ex) { + logger.error("Failed to read crate from input stream.", ex); + } + } + + @Override + public ObjectNode readMetadataJson(InputStream stream) { + if (!isExtracted) { + this.readCrate(stream); + } + + ObjectMapper objectMapper = MyObjectMapper.getMapper(); + File jsonMetadata = temporaryFolder.resolve("ro-crate-metadata.json").toFile(); + + try { + return objectMapper.readTree(jsonMetadata).deepCopy(); + } catch (IOException e) { + logger.error("Failed to deserialize crate metadata.", e); + return null; + } + } + + @Override + public File readContent(InputStream stream) { + if (!isExtracted) { + this.readCrate(stream); + } + return temporaryFolder.toFile(); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/util/ZipUtil.java b/src/main/java/edu/kit/datamanager/ro_crate/util/ZipUtil.java new file mode 100644 index 00000000..c1da0137 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/util/ZipUtil.java @@ -0,0 +1,93 @@ +package edu.kit.datamanager.ro_crate.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; +import net.lingala.zip4j.model.ZipParameters; + +/** + * + * @author jejkal + */ +public class ZipUtil { + + /** + * Adds a folder and its contents to a ZipOutputStream. + * + * @param zipOutputStream The ZipOutputStream to which the folder will be added. + * @param folder The folder to be added. + * @param parentPath The path in the zip file where the folder will be added. + * @throws IOException If an I/O error occurs. + */ + public static void addFolderToZipStream( + ZipOutputStream zipOutputStream, + File folder, + String parentPath + ) throws IOException { + if (!folder.exists() || !folder.isDirectory()) { + throw new IllegalArgumentException( + "The provided folder path is not a valid directory: %s" + .formatted(folder.getAbsolutePath()) + ); + } + + File[] files = folder.listFiles(); + if (files == null) { + return; + } + + for (File file : files) { + String zipEntryPath = parentPath.isEmpty() ? file.getName() : parentPath + "/" + file.getName(); + if (file.isDirectory()) { + addFolderToZipStream(zipOutputStream, file.getAbsolutePath(), zipEntryPath); + } else { + addFileToZipStream(zipOutputStream, file, zipEntryPath); + } + } + } + + /** + * Adds a folder and its contents to a ZipOutputStream. + * @param zipOutputStream The ZipOutputStream to which the folder will be added. + * @param folderPath The path of the folder to be added. + * @param parentPath The path in the zip file where the folder will be added. + * @throws IOException If an I/O error occurs. + */ + public static void addFolderToZipStream( + ZipOutputStream zipOutputStream, + String folderPath, + String parentPath + ) throws IOException { + addFolderToZipStream(zipOutputStream, new File(folderPath), parentPath); + } + + /** + * Adds a file to a ZipOutputStream. + * + * @param zipOutputStream The ZipOutputStream to which the file will be added. + * @param file The file to be added. + * @param zipEntryPath The path in the zip file where the file will be added. + * @throws IOException If an I/O error occurs. + */ + public static void addFileToZipStream( + ZipOutputStream zipOutputStream, + File file, + String zipEntryPath + ) throws IOException { + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setFileNameInZip(zipEntryPath); + zipOutputStream.putNextEntry(zipParameters); + + try (InputStream inputStream = new FileInputStream(file)) { + byte[] buffer = new byte[4096]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + zipOutputStream.write(buffer, 0, len); + } + } + + zipOutputStream.closeEntry(); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/CrateWriter.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/CrateWriter.java new file mode 100644 index 00000000..440be0c4 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/CrateWriter.java @@ -0,0 +1,30 @@ +package edu.kit.datamanager.ro_crate.writer; + +import edu.kit.datamanager.ro_crate.Crate; +import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation; +import edu.kit.datamanager.ro_crate.validation.Validator; + +/** + * The class used for writing (exporting) crates. The class uses a strategy + * pattern for writing crates as different formats. (zip, folders, etc.) + */ +public class CrateWriter { + + private final GenericWriterStrategy strategy; + + public CrateWriter(GenericWriterStrategy strategy) { + this.strategy = strategy; + } + + /** + * This method saves the crate to a destination provided. + * + * @param crate the crate to write. + * @param destination the location where the crate should be written. + */ + public void save(Crate crate, DESTINATION destination) { + Validator defaultValidation = new Validator(new JsonSchemaValidation()); + defaultValidation.validate(crate); + this.strategy.save(crate, destination); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/FolderStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/FolderStrategy.java new file mode 100644 index 00000000..b2585637 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/FolderStrategy.java @@ -0,0 +1,63 @@ +package edu.kit.datamanager.ro_crate.writer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.kit.datamanager.ro_crate.Crate; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; +import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * A class for writing a crate to a folder. + * + * @author Nikola Tzotchev on 9.2.2022 г. + * @version 1 + */ +public class FolderStrategy implements GenericWriterStrategy { + + private static final Logger logger = LoggerFactory.getLogger(FolderStrategy.class); + + @Override + public void save(Crate crate, String destination) { + File file = new File(destination); + try { + FileUtils.forceMkdir(file); + ObjectMapper objectMapper = MyObjectMapper.getMapper(); + JsonNode node = objectMapper.readTree(crate.getJsonMetadata()); + String str = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node); + InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); + + File json = new File(destination, "ro-crate-metadata.json"); + FileUtils.copyInputStreamToFile(inputStream, json); + inputStream.close(); + // save also the preview files to the crate destination + if (crate.getPreview() != null) { + crate.getPreview().saveAllToFolder(file); + } + for (var e : crate.getUntrackedFiles()) { + if (e.isDirectory()) { + FileUtils.copyDirectoryToDirectory(e, file); + } else { + FileUtils.copyFileToDirectory(e, file); + } + } + } catch (IOException e) { + logger.error("Error creating destination directory!", e); + } + for (DataEntity dataEntity : crate.getAllDataEntities()) { + try { + dataEntity.savetoFile(file); + } catch (IOException e) { + logger.error("Cannot save " + dataEntity.getId() + " to destination folder!", e); + } + } + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/FolderWriter.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/FolderWriter.java index 0be2a96d..5730104e 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/writer/FolderWriter.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/FolderWriter.java @@ -1,62 +1,11 @@ package edu.kit.datamanager.ro_crate.writer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import edu.kit.datamanager.ro_crate.Crate; -import edu.kit.datamanager.ro_crate.entities.data.DataEntity; -import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import org.apache.commons.io.FileUtils; - /** * A class for writing a crate to a folder. * * @author Nikola Tzotchev on 9.2.2022 г. - * @version 1 + * + * @deprecated Use {@link FolderStrategy} instead. */ -public class FolderWriter implements WriterStrategy { - - @Override - public void save(Crate crate, String destination) { - File file = new File(destination); - try { - FileUtils.forceMkdir(file); - ObjectMapper objectMapper = MyObjectMapper.getMapper(); - JsonNode node = objectMapper.readTree(crate.getJsonMetadata()); - String str = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node); - InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); - - File json = new File(destination, "ro-crate-metadata.json"); - FileUtils.copyInputStreamToFile(inputStream, json); - inputStream.close(); - // save also the preview files to the crate destination - if (crate.getPreview() != null) { - crate.getPreview().saveAllToFolder(file); - } - for (var e : crate.getUntrackedFiles()) { - if (e.isDirectory()) { - FileUtils.copyDirectoryToDirectory(e, file); - } else { - FileUtils.copyFileToDirectory(e, file); - } - } - } catch (IOException e) { - System.out.println("Error creating destination directory!"); - e.printStackTrace(); - } - for (DataEntity dataEntity : crate.getAllDataEntities()) { - try { - dataEntity.savetoFile(file); - } catch (IOException e) { - System.out.println("Cannot save " + dataEntity.getId() + " to destination folder!"); - e.printStackTrace(); - } - } - } -} +@Deprecated(since = "2.1.0", forRemoval = true) +public class FolderWriter extends FolderStrategy {} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/GenericWriterStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/GenericWriterStrategy.java new file mode 100644 index 00000000..6306b576 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/GenericWriterStrategy.java @@ -0,0 +1,19 @@ +package edu.kit.datamanager.ro_crate.writer; + +import edu.kit.datamanager.ro_crate.Crate; + +/** + * Generic interface for the strategy of the writer class. + * This allows for flexible output types when implementing different writing strategies. + * + * @param the type of the destination parameter + */ +public interface GenericWriterStrategy { + /** + * Saves the given crate to the specified destination. + * + * @param crate The crate to save + * @param destination The destination where the crate should be saved + */ + void save(Crate crate, DESTINATION destination); +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriter.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriter.java index 09b28c5a..9337d400 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriter.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriter.java @@ -1,31 +1,15 @@ package edu.kit.datamanager.ro_crate.writer; -import edu.kit.datamanager.ro_crate.Crate; -import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation; -import edu.kit.datamanager.ro_crate.validation.Validator; - /** - * The class used for writing (exporting) crates. - * The class uses a strategy pattern for writing crates as different formats. - * (zip, folders, etc.) + * The class used for writing (exporting) crates. The class uses a strategy + * pattern for writing crates as different formats. (zip, folders, etc.) + * + * @deprecated Use {@link CrateWriter} instead. */ -public class RoCrateWriter { - - private final WriterStrategy writer; - - public RoCrateWriter(WriterStrategy writer) { - this.writer = writer; - } +@Deprecated(since = "2.1.0", forRemoval = true) +public class RoCrateWriter extends CrateWriter { - /** - * This method saves the crate to a destination provided. - * - * @param crate the crate to write. - * @param destination the location where the crate should be written. - */ - public void save(Crate crate, String destination) { - Validator defaultValidation = new Validator(new JsonSchemaValidation()); - defaultValidation.validate(crate); - this.writer.save(crate, destination); - } + public RoCrateWriter(GenericWriterStrategy writer) { + super(writer); + } } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/WriterStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/WriterStrategy.java index 06be6585..12459673 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/writer/WriterStrategy.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/WriterStrategy.java @@ -1,13 +1,12 @@ package edu.kit.datamanager.ro_crate.writer; -import edu.kit.datamanager.ro_crate.Crate; - /** * Strategy for writing of crates. * * @author Nikola Tzotchev on 9.2.2022 г. * @version 1 + * + * @deprecated Use {@link GenericWriterStrategy} instead. */ -public interface WriterStrategy { - void save(Crate crate, String destination); -} +@Deprecated(since = "2.1.0", forRemoval = true) +public interface WriterStrategy extends GenericWriterStrategy {} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/Writers.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/Writers.java new file mode 100644 index 00000000..5b691ece --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/Writers.java @@ -0,0 +1,42 @@ +package edu.kit.datamanager.ro_crate.writer; + +import java.io.OutputStream; + +/** + * Utility class for creating instances of different crate writers. + * This class is not meant to be instantiated. + */ +public class Writers { + + /** + * Prevents instantiation of this utility class. + */ + private Writers() {} + + /** + * Creates a new instance of a crate writer that writes to a folder. + * + * @return a new instance of {@link CrateWriter} for writing to a folder + */ + public static CrateWriter newFolderWriter() { + return new CrateWriter<>(new FolderStrategy()); + } + + /** + * Creates a new instance of a crate writer that writes to a zip stream. + * + * @return a new instance of {@link CrateWriter} for writing to a zip stream + */ + public static CrateWriter newZipStreamWriter() { + return new CrateWriter<>(new ZipStreamStrategy()); + } + + /** + * Creates a new instance of a crate writer that writes to a zip file. + * + * @return a new instance of {@link CrateWriter} for writing to a zip file + */ + public static CrateWriter newZipPathWriter() { + return new CrateWriter<>(new ZipStrategy()); + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStrategy.java new file mode 100644 index 00000000..6a11df87 --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStrategy.java @@ -0,0 +1,68 @@ +package edu.kit.datamanager.ro_crate.writer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.kit.datamanager.ro_crate.Crate; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; +import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; +import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.exception.ZipException; +import net.lingala.zip4j.model.ZipParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * Implementation of the writing strategy to provide a way of writing crates to + * a zip archive. + */ +public class ZipStrategy implements GenericWriterStrategy { + + private static final Logger logger = LoggerFactory.getLogger(ZipStrategy.class); + + @Override + public void save(Crate crate, String destination) { + try (ZipFile zipFile = new ZipFile(destination)) { + saveMetadataJson(crate, zipFile); + saveDataEntities(crate, zipFile); + } catch (IOException e) { + // can not close ZipFile (threw Exception) + logger.error("Failed to write ro-crate to destination " + destination + ".", e); + } + } + + private void saveDataEntities(Crate crate, ZipFile zipFile) { + for (DataEntity dataEntity : crate.getAllDataEntities()) { + try { + dataEntity.saveToZip(zipFile); + } catch (ZipException e) { + logger.error("Could not save " + dataEntity.getId() + " to zip file!", e); + } + } + } + + private void saveMetadataJson(Crate crate, ZipFile zipFile) { + // write the metadata.json file + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setFileNameInZip("ro-crate-metadata.json"); + ObjectMapper objectMapper = MyObjectMapper.getMapper(); + try { + // we create an JsonNode only to have the file written pretty + JsonNode node = objectMapper.readTree(crate.getJsonMetadata()); + String str = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node); + try (InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))) { + // write the ro-crate-metadata + zipFile.addStream(inputStream, zipParameters); + } + if (crate.getPreview() != null) { + crate.getPreview().saveAllToZip(zipFile); + } + } catch (IOException e) { + logger.error("Exception writing ro-crate-metadata.json file to zip.", e); + } + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategy.java new file mode 100644 index 00000000..818c064c --- /dev/null +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategy.java @@ -0,0 +1,77 @@ +package edu.kit.datamanager.ro_crate.writer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import edu.kit.datamanager.ro_crate.Crate; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; +import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; +import net.lingala.zip4j.model.ZipParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of the writing strategy to provide a way of writing crates to + * a zip archive. + */ +public class ZipStreamStrategy implements GenericWriterStrategy { + + private static final Logger logger = LoggerFactory.getLogger(ZipStreamStrategy.class); + + @Override + public void save(Crate crate, OutputStream destination) { + try (ZipOutputStream zipFile = new ZipOutputStream(destination)) { + saveMetadataJson(crate, zipFile); + saveDataEntities(crate, zipFile); + } catch (IOException e) { + // can not close ZipOutputStream (threw Exception) + logger.error("Failed to save ro-crate to zip stream.", e); + } + } + + private void saveDataEntities(Crate crate, ZipOutputStream zipStream) { + for (DataEntity dataEntity : crate.getAllDataEntities()) { + try { + dataEntity.saveToStream(zipStream); + } catch (IOException e) { + logger.error("Could not save {} to zip stream!", dataEntity.getId(), e); + } + } + } + + private void saveMetadataJson(Crate crate, ZipOutputStream zipStream) { + try { + // write the metadata.json file + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setFileNameInZip("ro-crate-metadata.json"); + ObjectMapper objectMapper = MyObjectMapper.getMapper(); + // we create an JsonNode only to have the file written pretty + JsonNode node = objectMapper.readTree(crate.getJsonMetadata()); + String str = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node); + // write the ro-crate-metadata + + byte[] buff = new byte[4096]; + int readLen; + zipStream.putNextEntry(zipParameters); + try (InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))) { + while ((readLen = inputStream.read(buff)) != -1) { + zipStream.write(buff, 0, readLen); + } + } + zipStream.closeEntry(); + + if (crate.getPreview() != null) { + crate.getPreview().saveAllToStream(str, zipStream); + } + } catch (IOException e) { + logger.error("Exception writing ro-crate-metadata.json file to zip.", e); + } + } +} diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipWriter.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipWriter.java index bcc73f45..f5261003 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipWriter.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipWriter.java @@ -1,68 +1,10 @@ package edu.kit.datamanager.ro_crate.writer; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import edu.kit.datamanager.ro_crate.Crate; -import edu.kit.datamanager.ro_crate.entities.data.DataEntity; -import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import net.lingala.zip4j.ZipFile; -import net.lingala.zip4j.exception.ZipException; -import net.lingala.zip4j.model.ZipParameters; - /** - * Implementation of the writing strategy - * to provide a way of writing crates to a zip archive. + * Implementation of the writing strategy to provide a way of writing crates to + * a zip archive. + * + * @deprecated Use {@link ZipStrategy} instead. */ -public class ZipWriter implements WriterStrategy { - - @Override - public void save(Crate crate, String destination) { - try (ZipFile zipFile = new ZipFile(destination)) { - saveMetadataJson(crate, zipFile); - saveDataEntities(crate, zipFile); - } catch (IOException e) { - // can not close ZipFile (threw Exception) - } - } - - private void saveDataEntities(Crate crate, ZipFile zipFile) { - for (DataEntity dataEntity : crate.getAllDataEntities()) { - try { - dataEntity.saveToZip(zipFile); - } catch (ZipException e) { - System.out.println("could not save " + dataEntity.getId() + " to zip file!"); - } - } - } - - private void saveMetadataJson(Crate crate, ZipFile zipFile) { - try { - // write the metadata.json file - ZipParameters zipParameters = new ZipParameters(); - zipParameters.setFileNameInZip("ro-crate-metadata.json"); - ObjectMapper objectMapper = MyObjectMapper.getMapper(); - // we create an JsonNode only to have the file written pretty - JsonNode node = objectMapper.readTree(crate.getJsonMetadata()); - String str = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node); - InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); - // write the ro-crate-metadata - zipFile.addStream(inputStream, zipParameters); - inputStream.close(); - if (crate.getPreview() != null) { - crate.getPreview().saveAllToZip(zipFile); - } - } catch (ZipException | JsonProcessingException e) { - System.out.println("Exception writing ro-crate-metadata.json file to zip"); - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} +@Deprecated(since = "2.1.0", forRemoval = true) +public class ZipWriter extends ZipStrategy {} diff --git a/src/main/resources/json_schemas/entity_field_structure_schema.json b/src/main/resources/json_schemas/entity_field_structure_schema.json index 864422e9..d2112ebb 100644 --- a/src/main/resources/json_schemas/entity_field_structure_schema.json +++ b/src/main/resources/json_schemas/entity_field_structure_schema.json @@ -12,7 +12,6 @@ {"type": "boolean"}, {"type": "null"} ] - } }, { diff --git a/src/main/resources/templates/custom_preview.ftl b/src/main/resources/templates/custom_preview.ftl new file mode 100644 index 00000000..ae661702 --- /dev/null +++ b/src/main/resources/templates/custom_preview.ftl @@ -0,0 +1,201 @@ +<#ftl output_format="HTML"> + + + + + ${crateModel.crate.name!} + + + + + +

+

${crateModel.crate.name!}

+ + <#if crateModel.crate.description??> +

Description: ${crateModel.crate.description}

+ + + <#if crateModel.crate.license??> +

License: + <#if crateModel.crate.license?starts_with("http")> + ${crateModel.crate.license} + <#else> + ${crateModel.crate.license} + +

+ + + <#if crateModel.crate.datePublished??> +

Date Published: ${crateModel.crate.datePublished}

+ +
+ +
+ + <#if crateModel.crate.hasPart?? && crateModel.crate.hasPart?size gt 0> +
+

Parts

+
    + <#list crateModel.crate.hasPart as part> +
  • + ${part.name!} + <#if part.id??> + - ${part.id} + +
  • + +
+
+ + + <#if crateModel.datasets?? && crateModel.datasets?size gt 0> +
+

Datasets

+
    + <#list crateModel.datasets as dataset> +
  • + ${dataset.name!} + <#if dataset.id??> + - ${dataset.id} + + <#if dataset.description??> +
    ${dataset.description} + +
  • + +
+
+ + + <#if crateModel.files?? && crateModel.files?size gt 0> +
+

Files

+
    + <#list crateModel.files as file> +
  • + ${file.name!} + <#if file.id??> + - ${file.id} + + <#if file.description??> +
    ${file.description} + + <#if file.contentSize??> +
    Size: ${file.contentSize} + + <#if file.encodingFormat??> +
    Format: ${file.encodingFormat} + +
  • + +
+
+ + +
+ +
+

+ This human-readable preview has been created using ro-crate-java. +

+
+ + + \ No newline at end of file diff --git a/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesBenchmark.java b/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesBenchmark.java index 2e9c5e6d..b5bc255d 100644 --- a/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesBenchmark.java +++ b/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesBenchmark.java @@ -39,7 +39,7 @@ public static void main(String[] args) throws IOException { * @param numCrates the amount of crates. * @param numEntitiesProCrate the number of entities pro crate. * @param baseLocation the base location of the data entities. - * @throws IOException if the writting of the result ot a file fails. + * @throws IOException if the writing of the result of a file fails. */ public static void multipleCratesCreation( int numCrates, int numEntitiesProCrate, String baseLocation) throws IOException { @@ -59,7 +59,7 @@ public static void multipleCratesCreation( .addIdProperty("author", person) .build(); crate.addContextualEntity(person); - crate.addDataEntity(file, true); + crate.addDataEntity(file); } } Instant end = Instant.now(); diff --git a/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesWriteAndRead.java b/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesWriteAndRead.java index 026e5f6c..c24f3c5c 100644 --- a/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesWriteAndRead.java +++ b/src/performanceTest/java/edu/kit/datamanager/ro_crate/multiplecrates/MultipleCratesWriteAndRead.java @@ -67,7 +67,7 @@ public static void readWriteMultiple(int numCrates, int numEntitiesProCrate, Str .addIdProperty("author", person) .build(); crate.addContextualEntity(person); - crate.addDataEntity(file, true); + crate.addDataEntity(file); } RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); writer.save(crate, "crate" + i); diff --git a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/DeletionEntitiesPerformance.java b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/DeletionEntitiesPerformance.java index 17db8cce..70c62808 100644 --- a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/DeletionEntitiesPerformance.java +++ b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/DeletionEntitiesPerformance.java @@ -52,7 +52,7 @@ public static void deletionEntitiesTest(int numEntities, String baseLocation) th .addType("File") .addIdProperty("author", person) .build(); - crate.addDataEntity(file, true); + crate.addDataEntity(file); } for (int i = 0; i < numEntities; i++) { diff --git a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/LocalDataEntitiesPerformance.java b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/LocalDataEntitiesPerformance.java index 3dcaed87..9d966fe8 100644 --- a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/LocalDataEntitiesPerformance.java +++ b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/LocalDataEntitiesPerformance.java @@ -36,7 +36,7 @@ public static void main(String[] args) throws IOException { * * @param numEntities the amount of entities. * @param baseLocation their base location (where are the files present). - * @throws IOException if writint the results at the end fails. + * @throws IOException if writing the results at the end fails. */ public static void localDataEntitiesTest(int numEntities, String baseLocation) throws IOException { @@ -49,7 +49,7 @@ public static void localDataEntitiesTest(int numEntities, String baseLocation) .setId(baseLocation + "file" + i) .addType("File") .build(); - crate.addDataEntity(person, true); + crate.addDataEntity(person); } Instant end = Instant.now(); String duration = String.valueOf(Duration.between(start, end).toMillis() / 1000.f); diff --git a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/MixtureOfEntitiesPerformance.java b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/MixtureOfEntitiesPerformance.java index d4649fa2..d6c35232 100644 --- a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/MixtureOfEntitiesPerformance.java +++ b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/MixtureOfEntitiesPerformance.java @@ -52,7 +52,7 @@ public static void mixEntitiesTest(int numEntities, String baseLocation) throws .addType("File") .addIdProperty("author", person) .build(); - crate.addDataEntity(file, true); + crate.addDataEntity(file); } Instant end = Instant.now(); String duration = String.valueOf(Duration.between(start, end).toMillis() / 1000.f); diff --git a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/RemoteDataEntitiesPerformance.java b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/RemoteDataEntitiesPerformance.java index 489c6c01..6b685e1c 100644 --- a/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/RemoteDataEntitiesPerformance.java +++ b/src/performanceTest/java/edu/kit/datamanager/ro_crate/singlecratebenchmarks/RemoteDataEntitiesPerformance.java @@ -40,7 +40,7 @@ private static void remoteDataEntitiesTest(int numEntities, String baseId) throw .setId(baseId + i) .addType("File") .build(); - crate.addDataEntity(person, true); + crate.addDataEntity(person); } Instant end = Instant.now(); String duration = String.valueOf(Duration.between(start, end).toMillis() / 1000.f); 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 48c2d858..8f32213f 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java @@ -91,27 +91,27 @@ public static void compareCrateJsonToFileInResources(Crate crate1, String jsonFi public static boolean compareTwoDir(File dir1, File dir2) throws IOException { // compare the content of the two directories - Map result_map = FileUtils.listFiles(dir1, null, true) + Map compareWithMe = FileUtils.listFiles(dir1, null, true) .stream() .collect(Collectors.toMap(java.io.File::getName, Function.identity())); - Map input_map = FileUtils.listFiles(dir2, null, true) + Map testMe = FileUtils.listFiles(dir2, null, true) .stream() - .collect(Collectors.toMap(java.io.File::getName, Function.identity()));; + .collect(Collectors.toMap(java.io.File::getName, Function.identity())); - if (result_map.size() != input_map.size()) { + if (compareWithMe.size() != testMe.size()) { return false; } - for (String s : input_map.keySet()) { + for (String filename : testMe.keySet()) { // we do that because the ro-crate-metadata.json can be differently formatted, // or the order of the entities may be different // the same holds for the html file - if (s.equals("ro-crate-preview.html") || s.equals("ro-crate-metadata.json")) { - if (!result_map.containsKey(s)) { + if (filename.equals("ro-crate-preview.html") || filename.equals("ro-crate-metadata.json")) { + if (!compareWithMe.containsKey(filename)) { return false; } - } else if (!FileUtils.contentEqualsIgnoreEOL(input_map.get(s), result_map.get(s), null)) { + } else if (!FileUtils.contentEqualsIgnoreEOL(testMe.get(filename), compareWithMe.get(filename), null)) { return false; } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/context/ContextTest.java b/src/test/java/edu/kit/datamanager/ro_crate/context/ContextTest.java index f00e9d33..4995f272 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/context/ContextTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/context/ContextTest.java @@ -1,6 +1,6 @@ package edu.kit.datamanager.ro_crate.context; -import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -14,18 +14,26 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; public class ContextTest { RoCrateMetadataContext context; + RoCrateMetadataContext complexContext; @BeforeEach - void initContext() { + void initContext() throws IOException { // this will load the default context this.context = new RoCrateMetadataContext(); + + final String crateManifestPath = "/crates/extendedContextExample/ro-crate-metadata.json"; + ObjectMapper objectMapper = MyObjectMapper.getMapper(); + JsonNode jsonNode = objectMapper.readTree(ContextTest.class.getResourceAsStream(crateManifestPath)); + this.complexContext = new RoCrateMetadataContext(jsonNode.get("@context")); } @Test @@ -137,12 +145,12 @@ void deleteUrlTest() { } @Test - void doubledContextUrlsTest() throws JsonProcessingException { + void doubledContextUrlsTest() { String url = "www.example.com"; RoCrateMetadataContext context = new RoCrateMetadataContext(); - assertFalse(context.url.contains(url)); + assertFalse(context.urls.contains(url)); context.addToContextFromUrl(url); - assertTrue(context.url.contains(url)); + assertTrue(context.urls.contains(url)); RoCrateMetadataContext contextDoubled = new RoCrateMetadataContext(); contextDoubled.addToContextFromUrl(url); @@ -224,4 +232,50 @@ void testAbsoluteUrlType() { .build(); assertTrue(this.context.checkEntity(validEntity)); } + + @Test + void testSetDeleteGetPair() { + String key = "key"; + String value = "value"; + context.addToContext(key, value); + assertEquals(value, context.getValueOf(key)); + context.deleteValuePairFromContext(key); + assertNull(context.getValueOf(key)); + } + + @Test + void testReadDeleteGetPair() { + String key = "custom"; + String value = "_:"; + assertEquals(value, this.complexContext.getValueOf(key)); + this.complexContext.deleteValuePairFromContext(key); + assertNull(this.complexContext.getValueOf(key)); + this.complexContext.addToContext(key, value); + assertEquals(value, this.complexContext.getValueOf(key)); + } + + @Test + void testReadKeys() { + var expected = Set.of("custom", "owl", "datacite", "xsd", "rdfs"); + var given = this.complexContext.getKeys(); + for (String key : expected) { + assertTrue(given.contains(key), "Key " + key + " not found in the context"); + } + // prove immutability + assertThrows(UnsupportedOperationException.class, () -> given.add("newKey")); + } + + @Test + void testReadPairs() { + var expected = Set.of("custom", "owl", "datacite", "xsd", "rdfs"); + var given = this.complexContext.getPairs(); + var keys = given.keySet(); + var values = given.values(); + for (String key : expected) { + assertTrue(keys.contains(key), "Key " + key + " not found in the context"); + values.forEach(s -> assertFalse(s.isEmpty(), "Value for key " + key + " is empty")); + } + // prove immutability + assertThrows(UnsupportedOperationException.class, () -> given.put("newKey", "newValue")); + } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java index ee0d72e0..eddd93ee 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java @@ -8,6 +8,7 @@ import java.net.URISyntaxException; import java.util.Collection; +import edu.kit.datamanager.ro_crate.reader.Readers; import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.JsonNode; @@ -15,8 +16,6 @@ import edu.kit.datamanager.ro_crate.Crate; import edu.kit.datamanager.ro_crate.RoCrate; import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity; -import edu.kit.datamanager.ro_crate.reader.FolderReader; -import edu.kit.datamanager.ro_crate.reader.RoCrateReader; import edu.kit.datamanager.ro_crate.special.CrateVersion; import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation; import edu.kit.datamanager.ro_crate.validation.Validator; @@ -40,7 +39,7 @@ void testAppendConformsTo() throws URISyntaxException { @Test void testModificationOfDraftCrate() throws URISyntaxException { String path = this.getClass().getResource("/crates/spec-1.2-DRAFT/minimal-with-conformsTo-Array").getPath(); - RoCrate crate = new RoCrateReader(new FolderReader()).readCrate(path); + RoCrate crate = Readers.newFolderReader().readCrate(path); Collection existingProfiles = crate.getProfiles(); profile1 = new URI("https://example.com/myprofile/1.0"); profile2 = new URI("https://example.com/myprofile/2.0"); @@ -69,8 +68,8 @@ void testModificationOfDraftCrate() throws URISyntaxException { Collection newProfileState = modifiedCrate.getProfiles(); assertEquals(existingProfiles.size() + 2, newProfileState.size()); // new profiles are present - newProfileState.contains(profile1.toString()); - newProfileState.contains(profile2.toString()); + assertTrue(newProfileState.contains(profile1.toString())); + assertTrue(newProfileState.contains(profile2.toString())); // old profiles are present assertEquals( 0, diff --git a/src/test/java/edu/kit/datamanager/ro_crate/crate/OtherFilesTest.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/OtherFilesTest.java index b614322c..aeb275e9 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/crate/OtherFilesTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/crate/OtherFilesTest.java @@ -2,9 +2,8 @@ import edu.kit.datamanager.ro_crate.HelpFunctions; import edu.kit.datamanager.ro_crate.RoCrate; -import edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; +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; @@ -41,8 +40,8 @@ void testOtherFiles(@TempDir Path tempDir) throws IOException, URISyntaxExceptio HelpFunctions.compareCrateJsonToFileInResources(roCrate, "/json/crate/simple.json"); // write the crate in the temp dir - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(roCrate, crate.toFile().getAbsolutePath()); + Writers.newFolderWriter() + .save(roCrate, crate.toFile().getAbsolutePath()); HelpFunctions.compareCrateJsonToFileInResources(new File(Objects.requireNonNull(OtherFilesTest.class.getResource("/json/crate/simple.json")).toURI()), crate.resolve("ro-crate-metadata.json").toFile()); diff --git a/src/test/java/edu/kit/datamanager/ro_crate/crate/ReadAndWriteTest.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/ReadAndWriteTest.java index 8fcfd9b8..3c2e49b5 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/crate/ReadAndWriteTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/crate/ReadAndWriteTest.java @@ -3,12 +3,11 @@ import edu.kit.datamanager.ro_crate.Crate; import edu.kit.datamanager.ro_crate.HelpFunctions; import edu.kit.datamanager.ro_crate.RoCrate; -import edu.kit.datamanager.ro_crate.preview.CustomPreview; -import edu.kit.datamanager.ro_crate.reader.FolderReader; -import edu.kit.datamanager.ro_crate.reader.RoCrateReader; -import edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; +import edu.kit.datamanager.ro_crate.preview.StaticPreview; +import edu.kit.datamanager.ro_crate.reader.CrateReader; +import edu.kit.datamanager.ro_crate.reader.Readers; +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; @@ -31,15 +30,15 @@ void testReadingAndWriting(@TempDir Path path) throws IOException { FileUtils.writeStringToFile(fileInDir.toFile(), "fileN2", Charset.defaultCharset()); RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .setPreview(new CustomPreview(htmlFile.toFile(), htmlDir.toFile())) + .setPreview(new StaticPreview(htmlFile.toFile(), htmlDir.toFile())) .build(); Path writeDir = path.resolve("crate"); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(crate, writeDir.toAbsolutePath().toString()); + Writers.newFolderWriter() + .save(crate, writeDir.toAbsolutePath().toString()); - RoCrateReader reader = new RoCrateReader(new FolderReader()); + CrateReader reader = Readers.newFolderReader(); Crate newCrate = reader.readCrate(writeDir.toAbsolutePath().toString()); // the preview files as well as the metadata file should not be included here @@ -48,9 +47,10 @@ void testReadingAndWriting(@TempDir Path path) throws IOException { HelpFunctions.compareTwoCrateJson(newCrate, crate); } + @SuppressWarnings("DataFlowIssue") @Test void testReadCrateWithHasPartHierarchy() { - RoCrateReader reader = new RoCrateReader(new FolderReader()); + CrateReader reader = Readers.newFolderReader(); RoCrate crate = reader.readCrate(ReadAndWriteTest.class.getResource("/crates/hasPartHierarchy").getPath()); assertEquals(1, crate.getAllContextualEntities().size()); assertEquals(6, crate.getAllDataEntities().size()); diff --git a/src/test/java/edu/kit/datamanager/ro_crate/crate/TestRemoveAddContext.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/TestRemoveAddContext.java index f51d0351..b1f6e21b 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/crate/TestRemoveAddContext.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/crate/TestRemoveAddContext.java @@ -1,13 +1,28 @@ package edu.kit.datamanager.ro_crate.crate; -import org.junit.jupiter.api.Test; +import edu.kit.datamanager.ro_crate.reader.Readers; +import edu.kit.datamanager.ro_crate.HelpFunctions; +import edu.kit.datamanager.ro_crate.RoCrate; import com.fasterxml.jackson.core.JsonProcessingException; -import edu.kit.datamanager.ro_crate.HelpFunctions; -import edu.kit.datamanager.ro_crate.RoCrate; +import java.util.Objects; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; public class TestRemoveAddContext { + private RoCrate crateWithComplexContext; + + @BeforeEach + void setup() { + String crateManifestPath = "/crates/extendedContextExample/"; + crateManifestPath = Objects.requireNonNull(TestRemoveAddContext.class.getResource(crateManifestPath)).getPath(); + this.crateWithComplexContext = Readers.newFolderReader().readCrate(crateManifestPath); + } + @Test void testAddRemoveValuePair() throws JsonProcessingException { RoCrate crate = new RoCrate.RoCrateBuilder().addValuePairToContext("key", "value").build(); @@ -30,4 +45,37 @@ void testAddRemoveUrl() throws JsonProcessingException { } + @Test + void testReadDeleteGetContextPair() { + String key = "custom"; + String value = "_:"; + assertEquals(value, this.crateWithComplexContext.getMetadataContextValueOf(key)); + this.crateWithComplexContext.deleteValuePairFromContext(key); + assertNull(this.crateWithComplexContext.getMetadataContextValueOf(key)); + } + + @Test + void testReadContextKeys() { + var expected = Set.of("custom", "owl", "datacite", "xsd", "rdfs"); + var given = this.crateWithComplexContext.getMetadataContextKeys(); + for (String key : expected) { + assertTrue(given.contains(key), "Key " + key + " not found in the context"); + } + // prove immutability + assertThrows(UnsupportedOperationException.class, () -> given.add("newKey")); + } + + @Test + void testReadContextPairs() { + var expected = Set.of("custom", "owl", "datacite", "xsd", "rdfs"); + var given = this.crateWithComplexContext.getMetadataContextPairs(); + var keys = given.keySet(); + var values = given.values(); + for (String key : expected) { + assertTrue(keys.contains(key), "Key " + key + " not found in the context"); + values.forEach(s -> assertFalse(s.isEmpty(), "Value for key " + key + " is empty")); + } + // prove immutability + assertThrows(UnsupportedOperationException.class, () -> given.put("newKey", "newValue")); + } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/crate/preview/PreviewCrateTest.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/preview/PreviewCrateTest.java index bd7b9156..1ecc2062 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/crate/preview/PreviewCrateTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/crate/preview/PreviewCrateTest.java @@ -1,5 +1,7 @@ package edu.kit.datamanager.ro_crate.crate.preview; +import edu.kit.datamanager.ro_crate.writer.CrateWriter; +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; @@ -7,8 +9,7 @@ import edu.kit.datamanager.ro_crate.RoCrate; import edu.kit.datamanager.ro_crate.preview.AutomaticPreview; import edu.kit.datamanager.ro_crate.preview.CustomPreview; -import edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; +import edu.kit.datamanager.ro_crate.preview.StaticPreview; import java.io.IOException; import java.nio.charset.Charset; @@ -19,60 +20,72 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class PreviewCrateTest { - - @Test - void testAutomaticPreview(@TempDir Path temp) { - Path location = temp.resolve("ro_crate1"); - RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .setPreview(new AutomaticPreview()) - .build(); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(crate, location.toFile().getAbsolutePath()); - - assertTrue(Files.isRegularFile(location.resolve("ro-crate-preview.html"))); - } - @Test - void testAutomaticPreviewAddingLater(@TempDir Path temp) { - Path location = temp.resolve("ro_crate2"); - RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/").build(); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(crate, location.toFile().toString()); - assertFalse(location.resolve("ro-crate-preview.html").toFile().exists()); - crate.setRoCratePreview(new AutomaticPreview()); - writer.save(crate, location.toFile().toString()); - assertTrue(location.resolve("ro-crate-preview.html").toFile().exists()); - } + @Test + void testAutomaticPreview(@TempDir Path temp) { + Path location = temp.resolve("ro_crate1"); + RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .setPreview(new AutomaticPreview()) + .build(); + Writers.newFolderWriter() + .save(crate, location.toFile().getAbsolutePath()); + assertTrue(Files.isRegularFile(location.resolve("ro-crate-preview.html"))); + } - @Test - void testCustomPreviewOnlyHtmlFile(@TempDir Path temp) throws IOException { - Path location = temp.resolve("ro_crate3"); - Path previewFile = temp.resolve("random.html"); - FileUtils.writeStringToFile(previewFile.toFile(), "random html it is not important that it is valid for know", Charset.defaultCharset()); - RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .setPreview(new CustomPreview(previewFile.toFile())) - .build(); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(crate, location.toFile().toString()); - assertTrue(location.resolve("ro-crate-preview.html").toFile().exists()); - } + @Test + void testAutomaticPreviewAddingLater(@TempDir Path temp) { + Path location = temp.resolve("ro_crate2"); + RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .setPreview(null)//disable preview to allow to compare folders before and after + .build(); + CrateWriter writer = Writers.newFolderWriter(); + writer.save(crate, location.toFile().toString()); + assertFalse(location.resolve("ro-crate-preview.html").toFile().exists()); + crate.setRoCratePreview(new AutomaticPreview()); + writer.save(crate, location.toFile().toString()); + assertTrue(location.resolve("ro-crate-preview.html").toFile().exists()); + } - @Test - void testCustomPreviewHtmlFileWithOtherFiles(@TempDir Path temp) throws IOException { - Path location = temp.resolve("ro_crate4"); - Path previewFile = temp.resolve("random.html"); - FileUtils.writeStringToFile(previewFile.toFile(), "random html it is not important that it is valid for know", Charset.defaultCharset()); - Path dirHtml = temp.resolve("html_dir"); - Path css_file = dirHtml.resolve("test.css"); - FileUtils.writeStringToFile(css_file.toFile(), "random css it is not important that it is valid for know", Charset.defaultCharset()); - RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .setPreview(new CustomPreview(previewFile.toFile(), dirHtml.toFile())) - .build(); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(crate, location.toFile().toString()); - assertTrue(location.resolve("ro-crate-preview.html").toFile().exists()); - assertTrue(location.resolve("ro-crate-preview_files").toFile().exists()); - assertTrue(location.resolve("ro-crate-preview_files").resolve("test.css").toFile().exists()); - } + @Test + void testCustomPreview(@TempDir Path temp) { + Path location = temp.resolve("ro_crate1"); + RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .setPreview(new CustomPreview()) + .build(); + Writers.newFolderWriter() + .save(crate, location.toFile().getAbsolutePath()); + assertTrue(Files.isRegularFile(location.resolve("ro-crate-preview.html"))); + } + + @Test + void testStaticPreviewOnlyHtmlFile(@TempDir Path temp) throws IOException { + Path location = temp.resolve("ro_crate3"); + Path previewFile = temp.resolve("random.html"); + FileUtils.writeStringToFile(previewFile.toFile(), "random html it is not important that it is valid", Charset.defaultCharset()); + RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .setPreview(new StaticPreview(previewFile.toFile())) + .build(); + Writers.newFolderWriter() + .save(crate, location.toFile().getAbsolutePath()); + assertTrue(location.resolve("ro-crate-preview.html").toFile().exists()); + } + + @Test + void testStaticPreviewHtmlFileWithOtherFiles(@TempDir Path temp) throws IOException { + Path location = temp.resolve("ro_crate4"); + Path previewFile = temp.resolve("random.html"); + FileUtils.writeStringToFile(previewFile.toFile(), "random html it is not important that it is valid", Charset.defaultCharset()); + Path dirHtml = temp.resolve("html_dir"); + Path css_file = dirHtml.resolve("test.css"); + FileUtils.writeStringToFile(css_file.toFile(), "random css it is not important that it is valid", Charset.defaultCharset()); + RoCrate crate = new RoCrate.RoCrateBuilder("name", "description", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .setPreview(new StaticPreview(previewFile.toFile(), dirHtml.toFile())) + .build(); + Writers.newFolderWriter() + .save(crate, location.toFile().getAbsolutePath()); + assertTrue(location.resolve("ro-crate-preview.html").toFile().exists()); + assertTrue(location.resolve("ro-crate-preview_files").toFile().exists()); + assertTrue(location.resolve("ro-crate-preview_files").resolve("test.css").toFile().exists()); + } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/RealTest.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/RealTest.java index bdc132b8..ae206b7c 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/RealTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/RealTest.java @@ -12,8 +12,8 @@ import edu.kit.datamanager.ro_crate.entities.data.DataSetEntity; import edu.kit.datamanager.ro_crate.entities.data.FileEntity; import edu.kit.datamanager.ro_crate.externalproviders.personprovider.OrcidProvider; -import edu.kit.datamanager.ro_crate.reader.FolderReader; -import edu.kit.datamanager.ro_crate.reader.RoCrateReader; +import edu.kit.datamanager.ro_crate.reader.CrateReader; +import edu.kit.datamanager.ro_crate.reader.Readers; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; @@ -30,7 +30,7 @@ class RealTest { @Test void testWithIDRCProject(@TempDir Path temp) throws IOException { - RoCrateReader reader = new RoCrateReader(new FolderReader()); + CrateReader reader = Readers.newFolderReader(); final String locationMetadataFile = "/crates/other/idrc_project/ro-crate-metadata.json"; Crate crate = reader.readCrate(RealTest.class.getResource("/crates/other/idrc_project").getPath()); diff --git a/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/WorkflowHubTest.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/WorkflowHubTest.java index 245a0118..99b2b563 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/WorkflowHubTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/crate/realexamples/WorkflowHubTest.java @@ -2,11 +2,10 @@ import edu.kit.datamanager.ro_crate.Crate; import edu.kit.datamanager.ro_crate.HelpFunctions; -import edu.kit.datamanager.ro_crate.reader.RoCrateReader; -import edu.kit.datamanager.ro_crate.reader.ZipReader; -import edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; +import edu.kit.datamanager.ro_crate.reader.CrateReader; +import edu.kit.datamanager.ro_crate.reader.Readers; +import edu.kit.datamanager.ro_crate.writer.Writers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -16,14 +15,14 @@ public class WorkflowHubTest { + @SuppressWarnings("DataFlowIssue") @Test void testImportZip(@TempDir Path temp) throws IOException { - RoCrateReader reader = new RoCrateReader(new ZipReader()); + CrateReader reader = Readers.newZipPathReader(); Crate crate = reader.readCrate(WorkflowHubTest.class.getResource("/crates/workflowhub/workflow-109-5.crate.zip").getPath()); HelpFunctions.compareCrateJsonToFileInResources(crate, "/crates/workflowhub/workflow1/ro-crate-metadata.json"); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(crate, temp.toString()); + Writers.newFolderWriter().save(crate, temp.toString()); HelpFunctions.compareTwoDir(temp.toFile(), new File(WorkflowHubTest.class.getResource("/crates/workflowhub/workflow1/").getPath())); } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/externalproviders/RorProviderTest.java b/src/test/java/edu/kit/datamanager/ro_crate/externalproviders/RorProviderTest.java index 5880ed72..9300e157 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/externalproviders/RorProviderTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/externalproviders/RorProviderTest.java @@ -5,6 +5,7 @@ import edu.kit.datamanager.ro_crate.HelpFunctions; import edu.kit.datamanager.ro_crate.entities.contextual.OrganizationEntity; import edu.kit.datamanager.ro_crate.externalproviders.organizationprovider.RorProvider; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -18,23 +19,25 @@ */ public class RorProviderTest { - @Test - void testExternalRorProvider() throws IOException { - OrganizationEntity organizationEntity = RorProvider.getOrganization("https://ror.org/04t3en479"); - assertNotNull(organizationEntity); - HelpFunctions.compareEntityWithFile(organizationEntity, "/json/entities/contextual/rorkit.json"); - } - - @Test - void testInvalidRorUrl() { - assertThrows(IllegalArgumentException.class, () -> { - RorProvider.getOrganization("https://notror.org/04t3en479"); - }); - } - - @Test - void testInvalidRorId() { - OrganizationEntity organizationEntity = RorProvider.getOrganization("https://ror.org/42"); - assertNull(organizationEntity); - } + @Test + void testExternalRorProvider() throws IOException { + OrganizationEntity organizationEntity = RorProvider.getOrganization("https://ror.org/04t3en479"); + assertNotNull(organizationEntity); + Assertions.assertEquals("https://ror.org/04t3en479", organizationEntity.getProperty("@id").asText()); + Assertions.assertEquals("https://ror.org/04t3en479", organizationEntity.getProperty("url").asText()); + Assertions.assertEquals("Karlsruhe Institute of Technology", organizationEntity.getProperty("name").asText()); + } + + @Test + void testInvalidRorUrl() { + assertThrows(IllegalArgumentException.class, () -> { + RorProvider.getOrganization("https://notror.org/04t3en479"); + }); + } + + @Test + void testInvalidRorId() { + OrganizationEntity organizationEntity = RorProvider.getOrganization("https://ror.org/42"); + assertNull(organizationEntity); + } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/preview/PreviewTest.java b/src/test/java/edu/kit/datamanager/ro_crate/preview/PreviewTest.java index e8c7d2ac..2db987b3 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/preview/PreviewTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/preview/PreviewTest.java @@ -11,108 +11,160 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import org.junit.jupiter.api.Assertions; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class PreviewTest { - @Test - void customPreviewSaveToFolderTest(@TempDir Path dir) throws IOException { - var file1 = dir.resolve("file.html"); - FileUtils.writeStringToFile(file1.toFile(), "random html it is not important that it is valid for know", Charset.defaultCharset()); + @Test + void staticPreviewSaveToFolderTest(@TempDir Path dir) throws IOException { + var file1 = dir.resolve("file.html"); + FileUtils.writeStringToFile(file1.toFile(), "random html, does not need to be valid for this test", Charset.defaultCharset()); - var file2 = dir.resolve("directory"); - var fileInDir = file2.resolve("fileInDir.html"); - FileUtils.writeStringToFile(fileInDir.toFile(), "dajkdlfjdsklafj alksfjdalk fjl", Charset.defaultCharset()); - CustomPreview customPreview = new CustomPreview(file1.toFile(), file2.toFile()); + var file2 = dir.resolve("directory"); + var fileInDir = file2.resolve("fileInDir.html"); + FileUtils.writeStringToFile(fileInDir.toFile(), "dajkdlfjdsklafj alksfjdalk fjl", Charset.defaultCharset()); + StaticPreview customPreview = new StaticPreview(file1.toFile(), file2.toFile()); - customPreview.saveAllToFolder(dir.resolve("result").toFile()); + FileUtils.forceMkdir(dir.resolve("result").toFile()); + customPreview.saveAllToFolder(dir.resolve("result").toFile()); - var e = dir.resolve("result"); + var e = dir.resolve("result"); - var roPreview = e.resolve("ro-crate-preview.html"); - var roDir = e.resolve("ro-crate-preview_files"); - var roDirFile = roDir.resolve("fileInDir.html"); - assertTrue(Files.isRegularFile(roPreview)); - assertTrue(Files.isDirectory(roDir)); - assertTrue(Files.isRegularFile(roDirFile)); + var roPreview = e.resolve("ro-crate-preview.html"); + var roDir = e.resolve("ro-crate-preview_files"); + var roDirFile = roDir.resolve("fileInDir.html"); + assertTrue(Files.isRegularFile(roPreview)); + assertTrue(Files.isDirectory(roDir)); + assertTrue(Files.isRegularFile(roDirFile)); - assertTrue(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), file1.toFile(), String.valueOf(Charset.defaultCharset()))); - assertFalse(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); + assertTrue(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), file1.toFile(), String.valueOf(Charset.defaultCharset()))); + assertFalse(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); - assertTrue(FileUtils.contentEqualsIgnoreEOL(roDirFile.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); - } + assertTrue(FileUtils.contentEqualsIgnoreEOL(roDirFile.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); + } + + @Test + void staticPreviewSaveToZip(@TempDir Path dir) throws IOException { + var file1 = dir.resolve("file.html"); + FileUtils.writeStringToFile(file1.toFile(), "random html, does not need to be valid for this test", Charset.defaultCharset()); + + var file2 = dir.resolve("directory"); + var fileInDir = file2.resolve("fileInDir.html"); + FileUtils.writeStringToFile(fileInDir.toFile(), "dajkdlfjdsklafj alksfjdalk fjl", Charset.defaultCharset()); + StaticPreview customPreview = new StaticPreview(file1.toFile(), file2.toFile()); + + customPreview.saveAllToZip(new ZipFile(dir.resolve("destination.zip").toFile())); + try (ZipFile zf = new ZipFile(dir.resolve("destination.zip").toFile())) { + zf.extractAll(dir.resolve("extracted").toAbsolutePath().toString()); + } + + var e = dir.resolve("extracted"); + var roPreview = e.resolve("ro-crate-preview.html"); + var roDir = e.resolve("ro-crate-preview_files"); + var roDirFile = roDir.resolve("fileInDir.html"); + assertTrue(Files.isRegularFile(roPreview)); + assertTrue(Files.isDirectory(roDir)); + assertTrue(Files.isRegularFile(roDirFile)); + + assertTrue(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), file1.toFile(), String.valueOf(Charset.defaultCharset()))); + assertFalse(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); + + assertTrue(FileUtils.contentEqualsIgnoreEOL(roDirFile.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); + } - @Test - void customPreviewSaveToZip(@TempDir Path dir) throws IOException { - var file1 = dir.resolve("file.html"); - FileUtils.writeStringToFile(file1.toFile(), "random html it is not important that it is valid for know", Charset.defaultCharset()); + @Test + void testAutomaticPreviewAddToFolder(@TempDir Path dir) throws IOException { + AutomaticPreview automaticPreview = new AutomaticPreview(); - var file2 = dir.resolve("directory"); - var fileInDir = file2.resolve("fileInDir.html"); - FileUtils.writeStringToFile(fileInDir.toFile(), "dajkdlfjdsklafj alksfjdalk fjl", Charset.defaultCharset()); - CustomPreview customPreview = new CustomPreview(file1.toFile(), file2.toFile()); + InputStream crateJson = PreviewTest.class.getResourceAsStream("/crates/other/idrc_project/ro-crate-metadata.json"); + Path crate = dir.resolve("crate"); + // this crate will not have a json file + FileUtils.forceMkdir(crate.toFile()); + FileUtils.copyInputStreamToFile(crateJson, crate.resolve("ro-crate-metadata.json").toFile()); + automaticPreview.saveAllToFolder(crate.toFile()); + + // there should be a html file generated + assertTrue(Files.isRegularFile(crate.resolve("ro-crate-preview.html"))); + } + + @Test + void testAutomaticPreviewZip(@TempDir Path dir) throws IOException { + AutomaticPreview automaticPreview = new AutomaticPreview(); + InputStream crateJson = PreviewTest.class.getResourceAsStream("/crates/other/idrc_project/ro-crate-metadata.json"); + Path crate = dir.resolve("crate"); + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setFileNameInZip("ro-crate-metadata.json"); + + ZipFile zipFile = new ZipFile(dir.resolve("test.zip").toFile()); + zipFile.addStream(crateJson, zipParameters); + crateJson.close(); + + automaticPreview.saveAllToZip(zipFile); + + try { + // this should trow an exception but not stop the execution + ZipFile randomZipFile = new ZipFile(dir.resolve("dddd.zip").toFile()); + automaticPreview.saveAllToZip(randomZipFile); + Assertions.fail("Expected IOException when providing invalid ZIP file for preview."); + } catch (IOException ex) { + //ok + } + zipFile.extractAll(crate.toString()); + assertTrue(Files.isRegularFile(crate.resolve("ro-crate-preview.html"))); + } + + @Test + void testCustomPreviewAddToFolder(@TempDir Path dir) throws IOException { + CustomPreview customPreview = new CustomPreview(); + + InputStream crateJson = PreviewTest.class.getResourceAsStream("/crates/other/idrc_project/ro-crate-metadata.json"); + Path crate = dir.resolve("crate"); + // this crate will not have a json file + Path fakeCrate = dir.resolve("fakeCrate"); + FileUtils.forceMkdir(crate.toFile()); + FileUtils.copyInputStreamToFile(crateJson, crate.resolve("ro-crate-metadata.json").toFile()); + + customPreview.saveAllToFolder(crate.toFile()); + + try { + // this should trow an exception but not stop the execution + customPreview.saveAllToFolder(fakeCrate.toFile()); + Assertions.fail("Expected IOException when providing invalid ZIP file for preview."); + } catch (IOException ex) { + //ok + } + + // there should be a html file generated + assertTrue(Files.isRegularFile(crate.resolve("ro-crate-preview.html"))); + } - customPreview.saveAllToZip(new ZipFile(dir.resolve("destination.zip").toFile())); - try (ZipFile zf = new ZipFile(dir.resolve("destination.zip").toFile())) { - zf.extractAll(dir.resolve("extracted").toAbsolutePath().toString()); + @Test + void testCustomPreviewZip(@TempDir Path tmp) throws IOException { + CustomPreview customPreview = new CustomPreview(); + InputStream crateJson = PreviewTest.class.getResourceAsStream("/crates/other/idrc_project/ro-crate-metadata.json"); + Path crate = tmp.resolve("crate"); + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setFileNameInZip("ro-crate-metadata.json"); + + ZipFile zipFile = new ZipFile(tmp.resolve("test.zip").toFile()); + zipFile.addStream(crateJson, zipParameters); + crateJson.close(); + + customPreview.saveAllToZip(zipFile); + + try { + // this should trow an exception but not stop the execution + ZipFile randomZipFile = new ZipFile(tmp.resolve("dddd.zip").toFile()); + customPreview.saveAllToZip(randomZipFile); + Assertions.fail("Expected IOException when providing invalid input to preview."); + } catch (IOException ex) { + //ok + } + zipFile.extractAll(crate.toString()); + assertTrue(Files.isRegularFile(crate.resolve("ro-crate-preview.html"))); } - var e = dir.resolve("extracted"); - var roPreview = e.resolve("ro-crate-preview.html"); - var roDir = e.resolve("ro-crate-preview_files"); - var roDirFile = roDir.resolve("fileInDir.html"); - assertTrue(Files.isRegularFile(roPreview)); - assertTrue(Files.isDirectory(roDir)); - assertTrue(Files.isRegularFile(roDirFile)); - - assertTrue(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), file1.toFile(), String.valueOf(Charset.defaultCharset()))); - assertFalse(FileUtils.contentEqualsIgnoreEOL(roPreview.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); - - assertTrue(FileUtils.contentEqualsIgnoreEOL(roDirFile.toFile(), fileInDir.toFile(), String.valueOf(Charset.defaultCharset()))); - - } - - @Test - void testAutomaticPreviewAddToFolder(@TempDir Path dir) throws IOException { - AutomaticPreview automaticPreview = new AutomaticPreview(); - - InputStream crateJson = PreviewTest.class.getResourceAsStream("/crates/simple_crate/ro-crate-metadata.json"); - Path crate = dir.resolve("crate"); - // this crate will not have a json file - Path fakeCrate = dir.resolve("fakeCrate"); - FileUtils.forceMkdir(crate.toFile()); - FileUtils.copyInputStreamToFile(crateJson, crate.resolve("ro-crate-metadata.json").toFile()); - - automaticPreview.saveAllToFolder(crate.toFile()); - - // the program should not crash - automaticPreview.saveAllToFolder(fakeCrate.toFile()); - - // there should be a html file generated - assertTrue(Files.isRegularFile(crate.resolve("ro-crate-preview.html"))); - } - - @Test - void testAutomaticPreviewZip(@TempDir Path dir) throws IOException { - AutomaticPreview automaticPreview = new AutomaticPreview(); - InputStream crateJson = PreviewTest.class.getResourceAsStream("/crates/simple_crate/ro-crate-metadata.json"); - Path crate = dir.resolve("crate"); - ZipParameters zipParameters = new ZipParameters(); - zipParameters.setFileNameInZip("ro-crate-metadata.json"); - - ZipFile zipFile = new ZipFile(dir.resolve("test.zip").toFile()); - zipFile.addStream(crateJson, zipParameters); - crateJson.close(); - - automaticPreview.saveAllToZip(zipFile); - - // this should trow an exception but not stop the execution - ZipFile randomZipFile = new ZipFile(dir.resolve("dddd.zip").toFile()); - automaticPreview.saveAllToZip(randomZipFile); - - zipFile.extractAll(crate.toString()); - assertTrue(Files.isRegularFile(crate.resolve("ro-crate-preview.html"))); - } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/reader/CrateReaderTest.java b/src/test/java/edu/kit/datamanager/ro_crate/reader/CrateReaderTest.java new file mode 100644 index 00000000..bcf8948d --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/reader/CrateReaderTest.java @@ -0,0 +1,230 @@ +package edu.kit.datamanager.ro_crate.reader; + +import edu.kit.datamanager.ro_crate.Crate; +import edu.kit.datamanager.ro_crate.HelpFunctions; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; +import edu.kit.datamanager.ro_crate.entities.data.FileEntity; +import edu.kit.datamanager.ro_crate.writer.CrateWriter; +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.nio.charset.Charset; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Abstract class for testing crate readers. + * + * @param the source type of the reader strategy. Even though each implementation knows this T, + * we can't use it everywhere we'd like to as the code here needs to be generic. + * We therefore rely on methods to take a path (as we always assume local testing). + * Streams, for example, will therefore need to stream from/to a file. + * This parameter is only required to satisfy the generic reader strategy. + * @param the type of the reader strategy + */ +abstract class CrateReaderTest> { + + protected static RoCrate.RoCrateBuilder newBaseCrate() { + return new RoCrate.RoCrateBuilder( + "minimal", + "minimal RO_crate", + "2024", + "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + ); + } + + protected static FileEntity newDataEntity(Path filePath) throws IllegalArgumentException { + return new FileEntity.FileEntityBuilder() + .setLocationWithExceptions(filePath) + .setId(filePath.toFile().getName()) + .addProperty("name", "Survey responses") + .addProperty("contentSize", "26452") + .addProperty("encodingFormat", "text/csv") + .build(); + } + + /** + * Saves the crate with the writer fitting to the reader of {@link #readCrate(Path)}. + * + * @param crate the crate to save + * @param target the target path to the save location + * @throws IOException if an error occurs while saving the crate + */ + abstract protected void saveCrate(Crate crate, Path target) throws IOException; + + /** + * Reads the crate with the reader fitting to the writer of {@link #saveCrate(Crate, Path)}. + * @param source the source path to the crate + * @return the read crate + * @throws IOException if an error occurs while reading the crate + */ + abstract protected Crate readCrate(Path source) throws IOException; + + /** + * Creates a new reader strategy with a non-default temporary directory (if supported, default otherwise). + * + * @param tmpDirectory the temporary directory to use + * @param useUuidSubfolder whether to create a UUID subfolder under the temporary directory + * @return a new reader strategy + */ + abstract protected READER_STRATEGY newReaderStrategyWithTmp(Path tmpDirectory, boolean useUuidSubfolder); + + /** + * Reads the crate using the provided reader strategy. + * + * @param strategy the reader strategy to use + * @param source the source path to the crate + * @return the read crate + * @throws IOException if an error occurs while reading the crate + */ + abstract protected Crate readCrate(READER_STRATEGY strategy, Path source) throws IOException; + + @Test + void testReadingBasicCrate(@TempDir Path temp) throws IOException { + + RoCrate roCrate = newBaseCrate().build(); + Path zipPath = temp.resolve("result.zip"); + this.saveCrate(roCrate, zipPath); + Crate importedCrate = this.readCrate(zipPath); + HelpFunctions.compareTwoCrateJson(roCrate, importedCrate); + } + + @Test + void testWithFile(@TempDir Path temp) throws IOException { + Path csvPath = temp.resolve("survey-responses-2019.csv"); + FileUtils.touch(csvPath.toFile()); + FileUtils.writeStringToFile(csvPath.toFile(), "Dummy content", Charset.defaultCharset()); + RoCrate rawCrate = newBaseCrate() + .addDataEntity(newDataEntity(csvPath)) + .build(); + + assertEquals(1, rawCrate.getAllDataEntities().size()); + + Path zipPath = temp.resolve("result.zip"); + this.saveCrate(rawCrate, zipPath); + Crate importedCrate = this.readCrate(zipPath); + + HelpFunctions.compareTwoCrateJson(rawCrate, importedCrate); + } + + @Test + void testWithFileUrlEncoded(@TempDir Path temp) throws IOException { + // This URL will be encoded because of whitespaces + Path csvPath = temp.resolve("survey responses 2019.csv"); + FileUtils.touch(csvPath.toFile()); + FileUtils.writeStringToFile(csvPath.toFile(), "Dummy content", Charset.defaultCharset()); + RoCrate rawCrate = newBaseCrate() + .addDataEntity(newDataEntity(csvPath)) + .build(); + + DataEntity rawEntity = rawCrate.getAllDataEntities().iterator().next(); + assertTrue(rawEntity.getId().contains("survey")); + assertFalse(rawEntity.getId().contains(" ")); + assertEquals(1, rawCrate.getAllDataEntities().size()); + + Path zipPath = temp.resolve("result.zip"); + this.saveCrate(rawCrate, zipPath); + Crate importedCrate = this.readCrate(zipPath); + + DataEntity importedEntity = importedCrate.getAllDataEntities().iterator().next(); + assertTrue(importedEntity.getId().contains("survey")); + assertFalse(importedEntity.getId().contains(" ")); + assertEquals(1, importedCrate.getAllDataEntities().size()); + + HelpFunctions.compareTwoCrateJson(rawCrate, importedCrate); + } + + @Test + void TestWithFileWithLocation(@TempDir Path temp) throws IOException { + Path csvPath = temp.resolve("survey-responses-2019.csv"); + FileUtils.writeStringToFile(csvPath.toFile(), "Dummy content", Charset.defaultCharset()); + RoCrate rawCrate = newBaseCrate() + .addDataEntity(newDataEntity(csvPath)) + .setPreview(null)//disable preview to allow to compare folders before and after + .build(); + + // write to zip file and read via zip stream + Path zipPath = temp.resolve("result.zip"); + this.saveCrate(rawCrate, zipPath); + Crate importedCrate = this.readCrate(zipPath); + + // write raw crate and imported crate to folders and compare the results + Path rawCrateTarget = temp.resolve("rawCrateSaved"); + Path importedCrateTarget = temp.resolve("importedCrateSaved"); + { + // write raw crate and imported crate to two different directories + CrateWriter writer = Writers.newFolderWriter(); + writer.save(rawCrate, rawCrateTarget.toString()); + writer.save(importedCrate, importedCrateTarget.toString()); + } + + assertTrue(HelpFunctions.compareTwoDir(rawCrateTarget.toFile(), importedCrateTarget.toFile())); + HelpFunctions.compareTwoCrateJson(rawCrate, importedCrate); + } + + @Test + void TestWithFileWithLocationAddEntity(@TempDir Path temp) throws IOException { + Path csvPath = temp.resolve("file.csv"); + FileUtils.writeStringToFile(csvPath.toFile(), "fakecsv.1", Charset.defaultCharset()); + RoCrate rawCrate = newBaseCrate() + .addDataEntity(newDataEntity(csvPath)) + .build(); + + // write to zip file and import via zip stream + Path zipPath = temp.resolve("result.zip"); + this.saveCrate(rawCrate, zipPath); + Crate importedCrate = this.readCrate(zipPath); + { + // modify the imported crate + Path newFile = temp.resolve("new_file"); + FileUtils.writeStringToFile(newFile.toFile(), "Some file content", Charset.defaultCharset()); + importedCrate.addDataEntity(new FileEntity.FileEntityBuilder() + .setEncodingFormat("setnew") + .setLocationWithExceptions(newFile) + .setId("new_file") + .build()); + } + // write raw crate to a folder + Path rawCrateTarget = temp.resolve("rawCrateSaved"); + Path importedCrateTarget = temp.resolve("importedCrateSaved"); + { + // write raw crate and imported crate to two different directories + CrateWriter writer = Writers.newFolderWriter(); + writer.save(rawCrate, rawCrateTarget.toString()); + writer.save(importedCrate, importedCrateTarget.toFile().toString()); + } + // assert the folders are different + assertFalse(HelpFunctions.compareTwoDir(rawCrateTarget.toFile(), importedCrateTarget.toFile())); + HelpFunctions.compareTwoMetadataJsonNotEqual(rawCrate, importedCrate); + // assert the importedCrateTarget folder contains newFile + assertTrue(importedCrateTarget.resolve("new_file").toFile().isFile()); + } + + @Test + void testReadingBasicCrateWithCustomPath(@TempDir Path temp) throws IOException { + RoCrate rawCrate = newBaseCrate().build(); + + // Write to zip file + Path zipPath = temp.resolve("result.zip"); + this.saveCrate(rawCrate, zipPath); + + // read again and compare using custom path for temporary extraction folder + // (if available, otherwise uses default) + Path differentFolder = temp.resolve("differentFolder"); + READER_STRATEGY strategy = this.newReaderStrategyWithTmp(differentFolder, true); + Crate importedCrate = this.readCrate(strategy, zipPath); + HelpFunctions.compareTwoCrateJson(rawCrate, importedCrate); + + { + // try it again without the UUID subfolder and test if the directory is being cleaned up (for coverage). + READER_STRATEGY strategyWithoutSubfolder = this.newReaderStrategyWithTmp(differentFolder, false); + Crate crate = this.readCrate(strategyWithoutSubfolder, zipPath); + HelpFunctions.compareTwoCrateJson(rawCrate, crate); + } + } +} diff --git a/src/test/java/edu/kit/datamanager/ro_crate/reader/FolderReaderTest.java b/src/test/java/edu/kit/datamanager/ro_crate/reader/FolderReaderTest.java index f119aef0..21850edd 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/reader/FolderReaderTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/reader/FolderReaderTest.java @@ -4,19 +4,12 @@ import edu.kit.datamanager.ro_crate.HelpFunctions; import edu.kit.datamanager.ro_crate.RoCrate; import edu.kit.datamanager.ro_crate.entities.contextual.PersonEntity; -import edu.kit.datamanager.ro_crate.entities.data.FileEntity; - -import edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; +import edu.kit.datamanager.ro_crate.writer.Writers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.Charset; import java.nio.file.Path; -import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.*; @@ -24,56 +17,61 @@ * @author Nikola Tzotchev on 9.2.2022 г. * @version 1 */ -class FolderReaderTest { +class FolderReaderTest extends CrateReaderTest { - private Path writeMetadataToFile(Path temp, RoCrate c1) throws IOException { - // Write metadata to file - Path f = temp.resolve("ro-crate-metadata.json"); - FileUtils.touch(f.toFile()); - FileUtils.writeStringToFile(f.toFile(), c1.getJsonMetadata(), Charset.defaultCharset()); - return f; + @Override + protected void saveCrate(Crate crate, Path target) { + Writers.newFolderWriter().save(crate, target.toAbsolutePath().toString()); + assertTrue(target.toFile().isDirectory()); } - @Test - void testReadingBasicCrate(@TempDir Path temp) throws IOException { - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .build(); - Path f = writeMetadataToFile(temp, roCrate); - // Read from written file - RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); - RoCrate res = roCrateFolderReader.readCrate(temp.toFile().toString()); - // Write metadata again - Path r = temp.resolve("output.txt"); - FileUtils.touch(r.toFile()); - FileUtils.writeStringToFile(r.toFile(), res.getJsonMetadata(), Charset.defaultCharset()); - // See if it is the same - assertTrue(FileUtils.contentEquals(f.toFile(), r.toFile())); + @Override + protected Crate readCrate(Path source) throws IOException { + return Readers.newFolderReader().readCrate(source.toAbsolutePath().toString()); } + @Override + protected FolderStrategy newReaderStrategyWithTmp(Path tmpDirectory, boolean useUuidSubfolder) { + // This strategy does not support a non-default temporary directory + // and will always use the default one. + // It also has no state we could make assertions on. + return new FolderStrategy(); + } + + @Override + protected Crate readCrate(FolderStrategy strategy, Path source) throws IOException { + return new CrateReader<>(strategy) + .readCrate(source.toAbsolutePath().toString()); + } + + /** + * The folder reader is state-less, so we should be able to read multiple crates + * with the same instance. + */ @Test void testMultipleReads(@TempDir Path temp1, @TempDir Path temp2) throws IOException { String id = "https://orcid.org/0000-0001-6121-5409"; PersonEntity person = new PersonEntity.PersonEntityBuilder() - .setId(id) - .setContactPoint("mailto:tim.luckett@uts.edu.au") - .setAffiliation("https://ror.org/03f0f6041") - .setFamilyName("Luckett") - .setGivenName("Tim") - .addProperty("name", "Tim Luckett") - .build(); + .setId(id) + .setContactPoint("mailto:tim.luckett@uts.edu.au") + .setAffiliation("https://ror.org/03f0f6041") + .setFamilyName("Luckett") + .setGivenName("Tim") + .addProperty("name", "Tim Luckett") + .build(); RoCrate c1 = new RoCrate.RoCrateBuilder("mini", "test", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/").build(); RoCrate c2 = new RoCrate.RoCrateBuilder("other", "with file", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addContextualEntity(person) - .build(); - writeMetadataToFile(temp1, c1); - writeMetadataToFile(temp2, c2); + .addContextualEntity(person) + .build(); + this.saveCrate(c1, temp1); + this.saveCrate(c2, temp2); // some first checks... assertEquals(0, c1.getAllContextualEntities().size()); assertEquals(1, c2.getAllContextualEntities().size()); // read both with the same reader - RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); - RoCrate c1_read = roCrateFolderReader.readCrate(temp1.toFile().toString()); - RoCrate c2_read = roCrateFolderReader.readCrate(temp2.toFile().toString()); + CrateReader reader = Readers.newFolderReader(); + RoCrate c1_read = reader.readCrate(temp1.toFile().toString()); + RoCrate c2_read = reader.readCrate(temp2.toFile().toString()); // check that the reference is not the same assertNotEquals(c1, c1_read); assertNotEquals(c2, c2_read); @@ -82,148 +80,4 @@ void testMultipleReads(@TempDir Path temp1, @TempDir Path temp2) throws IOExcept assertEquals(1, c2_read.getAllContextualEntities().size()); HelpFunctions.compareTwoMetadataJsonNotEqual(c1_read, c2_read); } - - @Test - void testWithFile(@TempDir Path temp) throws IOException { - Path cvs = temp.resolve("survey-responses-2019.csv"); - FileUtils.touch(cvs.toFile()); - FileUtils.writeStringToFile(cvs.toFile(), "fkdjaflkjfla", Charset.defaultCharset()); - - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .setLocationWithExceptions(cvs) - .setId(cvs.toFile().getName()) - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .build() - ) - .build(); - - assertEquals(1, roCrate.getAllDataEntities().size()); - - writeMetadataToFile(temp, roCrate); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); - RoCrate res = roCrateFolderReader.readCrate(temp.toFile().toString()); - HelpFunctions.compareTwoCrateJson(roCrate, res); - } - - @Test - void testWithFileUrlEncoded(@TempDir Path temp) throws IOException { - - // get the std output redirected, so we can see if there is something written - PrintStream standardOut = System.out; - ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); - System.setOut(new PrintStream(outputStreamCaptor)); - - Path csv = temp.resolve("survey responses 2019.csv"); // This URL will be encoded because of whitespaces - FileUtils.touch(csv.toFile()); - FileUtils.writeStringToFile(csv.toFile(), "fkdjaflkjfla", Charset.defaultCharset()); - - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .setLocationWithExceptions(csv) - .setId(csv.toFile().getName()) - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .build() - ) - .build(); - - writeMetadataToFile(temp, roCrate); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); - Crate res = roCrateFolderReader.readCrate(temp.toFile().toString()); - HelpFunctions.compareTwoCrateJson(roCrate, res); - - // Make sure we did not print any errors - assertEquals("", outputStreamCaptor.toString().trim()); - System.setOut(standardOut); - } - - @Test - void TestWithFileWithLocation(@TempDir Path temp) throws IOException { - Path file = temp.resolve("survey-responses-2019.csv"); - FileUtils.writeStringToFile(file.toFile(), "fakecsv.1", Charset.defaultCharset()); - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .setLocationWithExceptions(file) - .setId("survey-responses-2019.csv") - .build() - ) - .build(); - Path locationSource = temp.resolve("src"); - FileUtils.forceMkdir(locationSource.toFile()); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - - writer.save(roCrate, locationSource.toFile().toString()); - - writeMetadataToFile(temp, roCrate); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); - - Crate res = roCrateFolderReader.readCrate(locationSource.toFile().toString()); - - Path destinationDir = temp.resolve("result"); - FileUtils.forceMkdir(destinationDir.toFile()); - - writer.save(res, destinationDir.toFile().toString()); - - // that copies the directory locally to see its content - //FileUtils.copyDirectory(locationSource.toFile(), new File("test")); - assertTrue(HelpFunctions.compareTwoDir(locationSource.toFile(), destinationDir.toFile())); - HelpFunctions.compareTwoCrateJson(roCrate, res); - } - - @Test - void TestWithFileWithLocationAddEntity(@TempDir Path temp) throws IOException { - Path file = temp.resolve("file.csv"); - FileUtils.writeStringToFile(file.toFile(), "fakecsv.1", Charset.defaultCharset()); - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .setLocationWithExceptions(file) - .setId("survey-responses-2019.csv") - .build() - ) - .build(); - Path locationSource = temp.resolve("src"); - FileUtils.forceMkdir(locationSource.toFile()); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - - writer.save(roCrate, locationSource.toFile().toString()); - - writeMetadataToFile(temp, roCrate); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); - - Path newFile = temp.resolve("new_file"); - FileUtils.writeStringToFile(newFile.toFile(), "fkladjsl;fjasd;lfjda;lkf", Charset.defaultCharset()); - - Crate res = roCrateFolderReader.readCrate(locationSource.toFile().toString()); - res.addDataEntity(new FileEntity.FileEntityBuilder() - .setEncodingFormat("setnew") - .setLocationWithExceptions(newFile) - .setId("new_file") - .build()); - - Path destinationDir = temp.resolve("result"); - FileUtils.forceMkdir(destinationDir.toFile()); - - writer.save(res, destinationDir.toFile().toString()); - - assertFalse(HelpFunctions.compareTwoDir(locationSource.toFile(), destinationDir.toFile())); - HelpFunctions.compareTwoMetadataJsonNotEqual(roCrate, res); - } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java b/src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java index 24bdcb54..7b1df7df 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java @@ -29,7 +29,7 @@ public class RoCrateReaderSpec12Test { @Test void testReadingCrateWithConformsToArray() { String path = this.getClass().getResource("/crates/spec-1.2-DRAFT/minimal-with-conformsTo-Array").getPath(); - Crate crate = new RoCrateReader(new FolderReader()).readCrate(path); + Crate crate = Readers.newFolderReader().readCrate(path); JsonNode conformsTo = crate.getJsonDescriptor().getProperty("conformsTo"); assertTrue(conformsTo.isArray()); assertEquals(2, conformsTo.size()); diff --git a/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipReaderTest.java b/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipReaderTest.java index a4d50dd6..3611c834 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipReaderTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipReaderTest.java @@ -1,203 +1,44 @@ package edu.kit.datamanager.ro_crate.reader; import edu.kit.datamanager.ro_crate.Crate; -import edu.kit.datamanager.ro_crate.HelpFunctions; -import edu.kit.datamanager.ro_crate.RoCrate; -import edu.kit.datamanager.ro_crate.entities.data.FileEntity; -import edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; -import edu.kit.datamanager.ro_crate.writer.ZipWriter; +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.File; import java.io.IOException; -import java.nio.charset.Charset; import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; -class ZipReaderTest { - - @Test - void testReadingBasicCrate(@TempDir Path temp) throws IOException { - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .build(); - - Path zipPath = temp.resolve("result.zip"); - - RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); - // save the content of the roCrate to the dest zip - roCrateZipWriter.save(roCrate, zipPath.toString()); - - File zipFile = zipPath.toFile(); - assertTrue(zipFile.isFile()); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new ZipReader()); - Crate res = roCrateFolderReader.readCrate(zipFile.getAbsolutePath()); - HelpFunctions.compareTwoCrateJson(roCrate, res); - } - - - @Test - void testWithFile(@TempDir Path temp) throws IOException { - Path cvs = temp.resolve("survey-responses-2019.csv"); - FileUtils.touch(cvs.toFile()); - FileUtils.writeStringToFile(cvs.toFile(), "fkdjaflkjfla", Charset.defaultCharset()); - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .setLocationWithExceptions(cvs) - .setId(cvs.toFile().getName()) - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .build() - ) - .build(); - - assertEquals(1, roCrate.getAllDataEntities().size()); - - Path zipPath = temp.resolve("result.zip"); - - RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); - // save the content of the roCrate to the dest zip - roCrateZipWriter.save(roCrate, zipPath.toFile().getAbsolutePath()); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new ZipReader()); - Crate res = roCrateFolderReader.readCrate(zipPath.toFile().getAbsolutePath()); - - HelpFunctions.compareTwoCrateJson(roCrate, res); - } - - @Test - void TestWithFileWithLocation(@TempDir Path temp) throws IOException { - Path file = temp.resolve("survey-responses-2019.csv"); - FileUtils.writeStringToFile(file.toFile(), "fakecsv.1", Charset.defaultCharset()); - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .setLocationWithExceptions(file) - .setId("survey-responses-2019.csv") - .build() - ) - .build(); - - Path zipPath = temp.resolve("result.zip"); - - RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); - // save the content of the roCrate to the dest zip - roCrateZipWriter.save(roCrate, zipPath.toString()); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new ZipReader()); - Crate res = roCrateFolderReader.readCrate(zipPath.toString()); - - - Path locationSource = temp.resolve("expected"); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(roCrate, locationSource.toString()); - - Path destinationDir = temp.resolve("result"); - FileUtils.forceMkdir(destinationDir.toFile()); - writer.save(res, destinationDir.toString()); +class ZipReaderTest extends CrateReaderTest { - // that copies the directory locally to see its content - assertTrue(HelpFunctions.compareTwoDir(locationSource.toFile(), destinationDir.toFile())); - HelpFunctions.compareTwoCrateJson(roCrate, res); - } - - @Test - void TestWithFileWithLocationAddEntity(@TempDir Path temp) throws IOException { - Path file = temp.resolve("file.csv"); - FileUtils.writeStringToFile(file.toFile(), "fakecsv.1", Charset.defaultCharset()); - RoCrate roCrate = new RoCrate.RoCrateBuilder("minimal", "minimal RO_crate", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Survey responses") - .addProperty("contentSize", "26452") - .addProperty("encodingFormat", "text/csv") - .setLocationWithExceptions(file) - .setId("survey-responses-2019.csv") - .build() - ) - .build(); - - Path zipPath = temp.resolve("result.zip"); - - RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); - // save the content of the roCrate to the dest zip - roCrateZipWriter.save(roCrate, zipPath.toString()); - - RoCrateReader roCrateFolderReader = new RoCrateReader(new ZipReader()); - Crate res = roCrateFolderReader.readCrate(zipPath.toFile().getAbsolutePath()); - - - Path locationSource = temp.resolve("expected"); - RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); - writer.save(roCrate, locationSource.toString()); - - - Path newFile = temp.resolve("new_file"); - FileUtils.writeStringToFile(newFile.toFile(), "fkladjsl;fjasd;lfjda;lkf", Charset.defaultCharset()); - - res.addDataEntity(new FileEntity.FileEntityBuilder() - .setEncodingFormat("setnew") - .setLocationWithExceptions(newFile) - .setId("new_file") - .build()); - - Path destinationDir = temp.resolve("result"); - FileUtils.forceMkdir(destinationDir.toFile()); - writer.save(res, destinationDir.toFile().toString()); - - assertFalse(HelpFunctions.compareTwoDir(locationSource.toFile(), destinationDir.toFile())); - HelpFunctions.compareTwoMetadataJsonNotEqual(roCrate, res); - } - - @Test - void testReadingBasicCrateWithCustomPath(@TempDir Path temp) throws IOException { - RoCrate roCrate = new RoCrate.RoCrateBuilder( - "minimal", - "minimal RO_crate", - "2024", - "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .build(); - - Path zipPath = temp.resolve("result.zip"); - - RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); - roCrateZipWriter.save(roCrate, zipPath.toString()); - - File zipFile = zipPath.toFile(); - assertTrue(zipFile.isFile()); - - Path differentFolder = temp.resolve("differentFolder"); - ZipReader readerType = new ZipReader(differentFolder, true); - assertFalse(readerType.isExtracted()); - assertEquals(readerType.getTemporaryFolder().getFileName().toString(), readerType.getID()); - assertTrue(readerType.getTemporaryFolder().startsWith(differentFolder)); + @Override + protected void saveCrate(Crate crate, Path target) { + Writers.newZipPathWriter().save(crate, target.toAbsolutePath().toString()); + assertTrue(target.toFile().isFile()); + } - RoCrateReader roCrateFolderReader = new RoCrateReader(readerType); - Crate crate = roCrateFolderReader.readCrate(zipFile.getAbsolutePath()); - assertTrue(readerType.isExtracted()); - HelpFunctions.compareTwoCrateJson(roCrate, crate); + @Override + protected Crate readCrate(Path source) throws IOException { + return Readers.newZipPathReader().readCrate(source.toAbsolutePath().toString()); + } - { - // try it again without the UUID subfolder and test if the directory is being cleaned up (using coverage). - ZipReader newReaderType = new ZipReader(differentFolder, false); - assertFalse(newReaderType.isExtracted()); - assertNotEquals(newReaderType.getTemporaryFolder().getFileName().toString(), newReaderType.getID()); - assertTrue(newReaderType.getTemporaryFolder().startsWith(differentFolder)); + @Override + protected ZipStrategy newReaderStrategyWithTmp(Path tmpDirectory, boolean useUuidSubfolder) { + ZipStrategy strategy = new ZipStrategy(tmpDirectory, useUuidSubfolder); + assertFalse(strategy.isExtracted()); + if (useUuidSubfolder) { + assertEquals(strategy.getTemporaryFolder().getFileName().toString(), strategy.getID()); + } else { + assertEquals(strategy.getTemporaryFolder().getFileName().toString(), tmpDirectory.getFileName().toString()); + } + assertTrue(strategy.getTemporaryFolder().startsWith(tmpDirectory)); + return strategy; + } - RoCrateReader newRoCrateFolderReader = new RoCrateReader(newReaderType); - Crate crate2 = newRoCrateFolderReader.readCrate(zipFile.getAbsolutePath()); - assertTrue(newReaderType.isExtracted()); - HelpFunctions.compareTwoCrateJson(roCrate, crate2); + @Override + protected Crate readCrate(ZipStrategy strategy, Path source) throws IOException { + Crate importedCrate = new CrateReader<>(strategy) + .readCrate(source.toAbsolutePath().toString()); + assertTrue(strategy.isExtracted()); + return importedCrate; } - } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipStreamReaderTest.java b/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipStreamReaderTest.java new file mode 100644 index 00000000..33436c6c --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipStreamReaderTest.java @@ -0,0 +1,44 @@ +package edu.kit.datamanager.ro_crate.reader; + +import edu.kit.datamanager.ro_crate.Crate; +import edu.kit.datamanager.ro_crate.writer.Writers; + +import java.io.*; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + + +class ZipStreamReaderTest extends CrateReaderTest { + @Override + protected void saveCrate(Crate crate, Path target) throws IOException { + Writers.newZipStreamWriter().save(crate, new FileOutputStream(target.toFile())); + assertTrue(target.toFile().isFile()); + } + + @Override + protected Crate readCrate(Path source) throws IOException { + return Readers.newZipStreamReader().readCrate(new FileInputStream(source.toFile())); + } + + @Override + protected ZipStreamStrategy newReaderStrategyWithTmp(Path tmpDirectory, boolean useUuidSubfolder) { + ZipStreamStrategy strategy = new ZipStreamStrategy(tmpDirectory, useUuidSubfolder); + assertFalse(strategy.isExtracted()); + if (useUuidSubfolder) { + assertEquals(strategy.getTemporaryFolder().getFileName().toString(), strategy.getID()); + } else { + assertEquals(strategy.getTemporaryFolder().getFileName().toString(), tmpDirectory.getFileName().toString()); + } + assertTrue(strategy.getTemporaryFolder().startsWith(tmpDirectory)); + return strategy; + } + + @Override + protected Crate readCrate(ZipStreamStrategy strategy, Path source) throws IOException { + Crate importedCrate = new CrateReader<>(strategy) + .readCrate(new FileInputStream(source.toFile())); + assertTrue(strategy.isExtracted()); + return importedCrate; + } +} diff --git a/src/test/java/edu/kit/datamanager/ro_crate/writer/CrateWriterTest.java b/src/test/java/edu/kit/datamanager/ro_crate/writer/CrateWriterTest.java new file mode 100644 index 00000000..429fc450 --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/writer/CrateWriterTest.java @@ -0,0 +1,302 @@ +package edu.kit.datamanager.ro_crate.writer; + +import edu.kit.datamanager.ro_crate.Crate; +import edu.kit.datamanager.ro_crate.HelpFunctions; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.entities.data.DataSetEntity; +import edu.kit.datamanager.ro_crate.entities.data.FileEntity; +import edu.kit.datamanager.ro_crate.preview.AutomaticPreview; +import edu.kit.datamanager.ro_crate.preview.PreviewGenerator; +import net.lingala.zip4j.ZipFile; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +abstract class CrateWriterTest { + + /** + * Saves the crate with the writer fitting to this test class. + * + * @param crate the crate to save + * @param target the target path to the save location + * @throws IOException if an error occurs while saving the crate + */ + abstract protected void saveCrate(Crate crate, Path target) throws IOException; + + /** + * Test where the writer needs to rename files or folders in order to make a valid crate. + * The content will therefore not be equal to its source! + * + * @param tempDir the temporary directory given by junit for our test + * @throws IOException if an error occurs while writing the crate + */ + @Test + void testFilesBeingAdjusted(@TempDir Path tempDir) throws IOException { + Path correctCrate = tempDir.resolve("compare_with_me"); + Path pathToFile = correctCrate.resolve("you-will-need-to-rename-this-file.ai"); + Path pathToDir = correctCrate.resolve("you-will-need-to-rename-this-dir"); + this.createManualCrateStructure(correctCrate, pathToFile, pathToDir); + + Path writtenCrate = tempDir.resolve("written-crate"); + Path extractionPath = tempDir.resolve("checkMe"); + { + RoCrate builtCrate = getCrateWithFileAndDir(pathToFile, pathToDir) + .addDataEntity(new DataSetEntity.DataSetBuilder() + .addProperty("name", "Subdir") + .addProperty("description", "Some subdir") + .setLocationWithExceptions(pathToDir.resolve("subdir")) + .setId("lots_of_little_files/subdir-renamed/") + .build() + ) + .build(); + this.saveCrate(builtCrate, writtenCrate); + this.ensureCrateIsExtractedIn(writtenCrate, extractionPath); + } + + printFileTree(correctCrate); + printFileTree(extractionPath); + + // The actual file name should **not** appear in the crate + String fileName = pathToFile.getFileName().toString(); + assertFalse( + Files.isRegularFile(extractionPath.resolve(fileName)), + "The directory should not be present, because '%s' is a file in the crate".formatted(fileName) + ); + // Instead, the file should be present with the name of the ID + assertTrue( + Files.isRegularFile(extractionPath.resolve("cp7glop.ai")), + "The file 'cp7glop.ai' should be present and have the name adjusted to the ID" + ); + // The actual directory name should **not** appear in the crate + String dirName = pathToDir.getFileName().toString(); + assertFalse( + Files.isDirectory(extractionPath.resolve(dirName)), + "The directory should not be present, because '%s' is a file in the crate".formatted(dirName) + ); + // Instead, the directory should be present with the name of the ID + assertTrue( + Files.isDirectory(extractionPath.resolve("lots_of_little_files/")), + "The directory 'lots_of_little_files' should be present" + ); + assertTrue( + Files.isDirectory(extractionPath.resolve("lots_of_little_files/").resolve("subdir")), + "The directory 'lots_of_little_files/subdir' should be present" + ); + + /* + * As we added another DataSetEntity with location, the subdir should have a renamed copy as well + * Note: If this behavior is to be changed later on, we possibly need to change the documentation + * of {@link AbstractDataEntityBuilder#setLocation(Path)}. + */ + assertTrue( + Files.isDirectory(extractionPath.resolve("lots_of_little_files/").resolve("subdir-renamed")), + "The directory 'lots_of_little_files/subdir' should be present" + ); + } + + /** + * Test where the writer should make an exact copy of our defined folder. + * + * @param tempDir the temporary directory given by junit for our test + * @throws IOException if an error occurs while writing the crate + */ + @Test + void testWritingMakesCopy(@TempDir Path tempDir) throws IOException { + // We need a correct directory to compare with. + // It is built manually to ensure we meet our expectations. + // Reader-writer-consistency is tested at {@link CrateReaderTest} + Path correctCrate = tempDir.resolve("compare_with_me"); + Path pathToFile = correctCrate.resolve("cp7glop.ai"); + Path pathToDir = correctCrate.resolve("lots_of_little_files"); + + this.createManualCrateStructure(correctCrate, pathToFile, pathToDir); + + // Now use the builder to build the same crate independently. + // The files will be reused (we need a place to take a copy from) + RoCrate builtCrate = getCrateWithFileAndDir(pathToFile, pathToDir).build(); + + Path pathToZip = tempDir.resolve("written-needs_testing.zip"); + this.saveCrate(builtCrate, pathToZip); + + // extract the zip file to a temporary directory + Path extractionPath = tempDir.resolve("extracted_for_testing"); + this.ensureCrateIsExtractedIn(pathToZip, extractionPath); + printFileTree(correctCrate); + printFileTree(extractionPath); + + // compare the extracted directory with the correct one + assertTrue(HelpFunctions.compareTwoDir( + correctCrate.toFile(), + extractionPath.toFile())); + HelpFunctions.compareCrateJsonToFileInResources( + builtCrate, + "/json/crate/fileAndDir.json"); + } + + /** + * Test where the writer should only consider the files that are added to the metadata json. + * The crate should not contain any files that are not part of it. + * + * @param tempDir the temporary directory given by junit for our test + * @throws IOException if an error occurs while writing the crate + */ + @Test + void testWritingOnlyConsidersAddedFiles(@TempDir Path tempDir) throws IOException { + Path correctCrate = tempDir.resolve("compare_with_me"); + Path pathToFile = correctCrate.resolve("cp7glop.ai"); + Path pathToDir = correctCrate.resolve("lots_of_little_files"); + + this.createManualCrateStructure(correctCrate, pathToFile, pathToDir); + { + // This file is not part of the crate, and should therefore not be present + Path falseFile = correctCrate.resolve("new"); + FileUtils.writeStringToFile( + falseFile.toFile(), + "this file contains something else", + Charset.defaultCharset()); + } + + // create the RO_Crate including the files that should be present in it + RoCrate roCrate = getCrateWithFileAndDir(pathToFile, pathToDir).build(); + + Path pathToZip = tempDir.resolve("writing-needs_testing.zip"); + this.saveCrate(roCrate, pathToZip); + + + // extract and compare + Path extractionPath = tempDir.resolve("extracted_for_testing"); + ensureCrateIsExtractedIn(pathToZip, extractionPath); + printFileTree(correctCrate); + printFileTree(extractionPath); + + assertFalse(HelpFunctions.compareTwoDir( + correctCrate.toFile(), + extractionPath.toFile()), + "The crate should not contain the file that was not part of the metadata"); + HelpFunctions.compareCrateJsonToFileInResources( + roCrate, + "/json/crate/fileAndDir.json"); + } + + /** + * Prints the file tree of the given directory for debugging and understanding + * a test more quickly. + * + * @param directoryToPrint the directory to print + * @throws IOException if an error occurs while printing the file tree + */ + @SuppressWarnings("resource") + protected static void printFileTree(Path directoryToPrint) throws IOException { + // Print all files recursively in a tree structure for debugging + System.out.printf("Files in %s:%n", directoryToPrint.getFileName().toString()); + Files.walk(directoryToPrint) + .forEach(path -> { + if (!path.toAbsolutePath().equals(directoryToPrint.toAbsolutePath())) { + int depth = path.relativize(directoryToPrint).getNameCount(); + String prefix = " ".repeat(depth); + System.out.printf("%s%s%s%n", prefix, "└── ", path.getFileName()); + } + }); + } + + /** + * Ensures the crate is in extracted form in the given path. + * + * @param pathToCrate the path to the crate, may not be a folder yet + * @param expectedPath the path where the crate should be in extracted form + * @throws IOException if an error occurs while extracting the crate + */ + protected void ensureCrateIsExtractedIn(Path pathToCrate, Path expectedPath) throws IOException { + try (ZipFile zf = new ZipFile(pathToCrate.toFile())) { + zf.extractAll(expectedPath.toFile().getAbsolutePath()); + } + } + + /** + * Creates a crate structure manually. + * + * @param correctCrate the path to the crate + * @param pathToFile the path to the file + * @param pathToDir the path to the directory + * @throws IOException if an error occurs while creating the crate structure + */ + protected void createManualCrateStructure(Path correctCrate, Path pathToFile, Path pathToDir) throws IOException { + FileUtils.forceMkdir(correctCrate.toFile()); + InputStream fileJson = ZipStreamStrategyTest.class + .getResourceAsStream("/json/crate/fileAndDir.json"); + Assertions.assertNotNull(fileJson); + // fill the directory with expected files and dirs + // starting with the .json of our crate + Path json = correctCrate.resolve("ro-crate-metadata.json"); + FileUtils.copyInputStreamToFile(fileJson, json.toFile()); + // create preview + PreviewGenerator.generatePreview(correctCrate.toFile().getAbsolutePath()); + // create the files and directories + FileUtils.writeStringToFile(pathToFile.toFile(), "content of Local File", Charset.defaultCharset()); + // creates the directory and a subdirectory + Path subdir = pathToDir.resolve("subdir"); + FileUtils.forceMkdir(subdir.toFile()); + FileUtils.writeStringToFile( + subdir.resolve("subsubfirst.txt").toFile(), + "content of subsub file in subsubdir", + Charset.defaultCharset()); + FileUtils.writeStringToFile( + pathToDir.resolve("first.txt").toFile(), + "content of first file in dir", + Charset.defaultCharset()); + FileUtils.writeStringToFile( + pathToDir.resolve("second.txt").toFile(), + "content of second file in dir", + Charset.defaultCharset()); + FileUtils.writeStringToFile( + pathToDir.resolve("third.txt").toFile(), + "content of third file in dir", + Charset.defaultCharset()); + } + + /** + * Creates a crate resembling the one we manually create in these tests. + * + * @param pathToFile the file to add + * @param pathToSubdir the directory to add + * @return the crate builder + */ + protected RoCrate.RoCrateBuilder getCrateWithFileAndDir(Path pathToFile, Path pathToSubdir) { + return new RoCrate.RoCrateBuilder( + "Example RO-Crate", + "The RO-Crate Root Data Entity", + "2024", + "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + ) + .addDataEntity( + new FileEntity.FileEntityBuilder() + .addProperty("name", "Diagram showing trend to increase") + .addProperty("contentSize", "383766") + .addProperty("description", "Illustrator file for Glop Pot") + .setEncodingFormat("application/pdf") + .setLocationWithExceptions(pathToFile) + .setId("cp7glop.ai") + .build() + ) + .addDataEntity( + new DataSetEntity.DataSetBuilder() + .addProperty("name", "Too many files") + .addProperty("description", + "This directory contains many small files, that we're not going to describe in detail.") + .setLocationWithExceptions(pathToSubdir) + .setId("lots_of_little_files/") + .build() + ) + .setPreview(new AutomaticPreview()); + } +} diff --git a/src/test/java/edu/kit/datamanager/ro_crate/writer/FolderWriterTest.java b/src/test/java/edu/kit/datamanager/ro_crate/writer/FolderWriterTest.java index 7f2333f1..7a469465 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/writer/FolderWriterTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/writer/FolderWriterTest.java @@ -1,212 +1,26 @@ package edu.kit.datamanager.ro_crate.writer; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.nio.file.Files; import java.nio.file.Path; -import edu.kit.datamanager.ro_crate.HelpFunctions; -import edu.kit.datamanager.ro_crate.RoCrate; -import edu.kit.datamanager.ro_crate.entities.data.DataSetEntity; -import edu.kit.datamanager.ro_crate.entities.data.FileEntity; -import edu.kit.datamanager.ro_crate.preview.AutomaticPreview; -import edu.kit.datamanager.ro_crate.preview.PreviewGenerator; +import edu.kit.datamanager.ro_crate.Crate; import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; /** * @author Nikola Tzotchev on 9.2.2022 г. * @version 1 */ -class FolderWriterTest { - - - @Test - void writeToFolderCorrectNames(@TempDir Path tempDir) throws IOException { - Path fileWithoutID = tempDir.resolve("spo.txt"); - FileUtils.writeStringToFile(fileWithoutID.toFile(), "content", Charset.defaultCharset()); - Path file1 = tempDir.resolve("input.txt"); - FileUtils.writeStringToFile(file1.toFile(), "content of Local File", Charset.defaultCharset()); - Path dirInCrate = tempDir.resolve("dir"); - FileUtils.forceMkdir(dirInCrate.toFile()); - Path dirInDirInCrate = dirInCrate.resolve("last_dir"); - FileUtils.writeStringToFile(dirInCrate.resolve("first.txt").toFile(), - "content of first file in dir", Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInDirInCrate.resolve("second.txt").toFile(), - "content of second file in dir", - Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInCrate.resolve("third.txt").toFile(), - "content of third file in dir", - Charset.defaultCharset()); - - // create the RO_Crate including the files that should be present in it - RoCrate roCrate = new RoCrate.RoCrateBuilder("Example RO-Crate", - "The RO-Crate Root Data Entity", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Diagram showing trend to increase") - .addProperty("contentSize", "383766") - .addProperty("description", "Illustrator file for Glop Pot") - .setEncodingFormat("application/pdf") - .setLocationWithExceptions(file1) - .setId("cp7glop.ai") - .build() - ) - .addDataEntity( - new DataSetEntity.DataSetBuilder() - .addProperty("name", "Too many files") - .addProperty("description", - "This directory contains many small files, that we're not going to describe in detail.") - .setLocationWithExceptions(dirInCrate) - .setId("lots_of_little_files/") - .build() - ) - .setPreview(new AutomaticPreview()) - .build(); - - Path result = tempDir.resolve("dest"); - - RoCrateWriter folderRoCrateWriter = new RoCrateWriter(new FolderWriter()); - folderRoCrateWriter.save(roCrate, result.toFile().toString()); - // test if the names of the files in the crate are correct, - // when there is an ID the file should be called the same as the entity. - assertTrue(Files.isRegularFile(result.resolve("cp7glop.ai"))); - assertTrue(Files.isDirectory(result.resolve("lots_of_little_files/"))); - // assertTrue(Files.isRegularFile(result.resolve(fileWithoutID.getFileName()))); - } - - @Test - void writeToFolderTest(@TempDir Path tempDir) throws IOException { - RoCrateWriter folderRoCrateWriter = new RoCrateWriter(new FolderWriter()); - Path roDir = tempDir.resolve("ro_dir"); - FileUtils.forceMkdir(roDir.toFile()); - - // the .json of our crate - InputStream fileJson = - FolderWriterTest.class.getResourceAsStream("/json/crate/fileAndDir.json"); - - // fill the expected directory with files and dirs - - Path json = roDir.resolve("ro-crate-metadata.json"); - FileUtils.copyInputStreamToFile(fileJson, json.toFile()); - - PreviewGenerator.generatePreview(roDir.toString()); - - Path file1 = roDir.resolve("cp7glop.ai"); - FileUtils.writeStringToFile(file1.toFile(), "content of Local File", Charset.defaultCharset()); - Path dirInCrate = roDir.resolve("lots_of_little_files"); - FileUtils.forceMkdir(dirInCrate.toFile()); - Path dirInDirInCrate = dirInCrate.resolve("last_dir"); - FileUtils.writeStringToFile(dirInCrate.resolve("first.txt").toFile(), - "content of first file in dir", Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInDirInCrate.resolve("second.txt").toFile(), - "content of second file in dir", - Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInCrate.resolve("third.txt").toFile(), - "content of third file in dir", - Charset.defaultCharset()); - - // create the RO_Crate including the files that should be present in it - RoCrate roCrate = new RoCrate.RoCrateBuilder("Example RO-Crate", - "The RO-Crate Root Data Entity", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Diagram showing trend to increase") - .addProperty("contentSize", "383766") - .addProperty("description", "Illustrator file for Glop Pot") - .setEncodingFormat("application/pdf") - .setLocationWithExceptions(file1) - .setId("cp7glop.ai") - .build() - ) - .addDataEntity( - new DataSetEntity.DataSetBuilder() - .addProperty("name", "Too many files") - .addProperty("description", - "This directory contains many small files, that we're not going to describe in detail.") - .setLocationWithExceptions(dirInCrate) - .setId("lots_of_little_files/") - .build() - ) - .setPreview(new AutomaticPreview()) - .build(); - - Path result = tempDir.resolve("dest"); - folderRoCrateWriter.save(roCrate, result.toFile().toString()); - assertTrue(HelpFunctions.compareTwoDir(result.toFile(), roDir.toFile())); - - // just so we know the metadata is still valid - HelpFunctions.compareCrateJsonToFileInResources(roCrate, "/json/crate/fileAndDir.json"); - } - - @Test - void writeToFolderWrongTest(@TempDir Path tempDir) throws IOException { - RoCrateWriter folderRoCrateWriter = new RoCrateWriter(new FolderWriter()); - Path roDir = tempDir.resolve("ro_dir"); - FileUtils.forceMkdir(roDir.toFile()); - - // the .json of our crate - InputStream fileJson = - FolderWriterTest.class.getResourceAsStream("/json/crate/fileAndDir.json"); - - // fill the expected directory with files and dirs - - Path json = roDir.resolve("ro-crate-metadata.json"); - FileUtils.copyInputStreamToFile(fileJson, json.toFile()); - - PreviewGenerator.generatePreview(roDir.toString()); - - Path file1 = roDir.resolve("cp7glop.ai"); - FileUtils.writeStringToFile(file1.toFile(), "content of Local File", Charset.defaultCharset()); - Path dirInCrate = roDir.resolve("lots_of_little_files"); - FileUtils.forceMkdir(dirInCrate.toFile()); - Path dirInDirInCrate = dirInCrate.resolve("last_dir"); - FileUtils.writeStringToFile(dirInCrate.resolve("first.txt").toFile(), - "content of first file in dir", Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInDirInCrate.resolve("second.txt").toFile(), - "content of second file in dir", - Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInCrate.resolve("third.txt").toFile(), - "content of third file in dir", - Charset.defaultCharset()); - // false file, this test case should fal - Path falseFile = tempDir.resolve("new"); - FileUtils.writeStringToFile(falseFile.toFile(), "this file contains something else", Charset.defaultCharset()); - // create the RO_Crate including the files that should be present in it - RoCrate roCrate = new RoCrate.RoCrateBuilder("Example RO-Crate", - "The RO-Crate Root Data Entity", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Diagram showing trend to increase") - .addProperty("contentSize", "383766") - .addProperty("description", "Illustrator file for Glop Pot") - .setEncodingFormat("application/pdf") - .setLocationWithExceptions(falseFile) - .setId("cp7glop.ai") - .build() - ) - .addDataEntity( - new DataSetEntity.DataSetBuilder() - .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.") - .setLocationWithExceptions(dirInCrate) - .setId("lots_of_little_files/") - .build() - ) - .build(); - - Path result = tempDir.resolve("dest"); - folderRoCrateWriter.save(roCrate, result.toFile().toString()); - assertFalse(HelpFunctions.compareTwoDir(result.toFile(), roDir.toFile())); - - HelpFunctions.compareCrateJsonToFileInResources(roCrate, "/json/crate/fileAndDir.json"); - } +class FolderWriterTest extends CrateWriterTest { + + @Override + protected void saveCrate(Crate crate, Path target) throws IOException { + Writers.newFolderWriter() + .save(crate, target.toAbsolutePath().toString()); + } + + @Override + protected void ensureCrateIsExtractedIn(Path pathToCrate, Path expectedPath) throws IOException { + FileUtils.copyDirectory(pathToCrate.toFile(), expectedPath.toFile()); + } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java b/src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java index b6d4ed40..90161e4d 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java @@ -10,13 +10,12 @@ import java.nio.file.Path; import java.nio.file.Paths; +import edu.kit.datamanager.ro_crate.reader.Readers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import edu.kit.datamanager.ro_crate.Crate; import edu.kit.datamanager.ro_crate.HelpFunctions; -import edu.kit.datamanager.ro_crate.reader.FolderReader; -import edu.kit.datamanager.ro_crate.reader.RoCrateReader; class RoCrateWriterSpec12Test { @@ -27,14 +26,11 @@ void writeDoesNotModifyTest(@TempDir Path tempDir) throws IOException, URISyntax URL internalOriginalCrateURL = this.getClass().getResource("/" + internalOriginalCratePath); assertNotNull(internalOriginalCrateURL); - Crate crate = new RoCrateReader(new FolderReader()).readCrate(internalOriginalCrateURL.getPath()); + Crate crate = Readers.newFolderReader().readCrate(internalOriginalCrateURL.getPath()); Path targetDir = tempDir.resolve("spec12writeUnmodified"); - { - // save to disk - RoCrateWriter folderRoCrateWriter = new RoCrateWriter(new FolderWriter()); - folderRoCrateWriter.save(crate, targetDir.toFile().getPath()); - } + Writers.newFolderWriter() + .save(crate, targetDir.toAbsolutePath().toString()); // compare directories Path srcDir = Paths.get(internalOriginalCrateURL.toURI()); @@ -51,7 +47,7 @@ void writeDoesNotModifyTest(@TempDir Path tempDir) throws IOException, URISyntax // original metadata file new File(srcDir.resolve("ro-crate-metadata.json").toString())); // Compare loaded crate object with crate object made of export - Crate crate2 = new RoCrateReader(new FolderReader()).readCrate(targetDir.toString()); + Crate crate2 = Readers.newFolderReader().readCrate(targetDir.toString()); HelpFunctions.compareTwoCrateJson(crate, crate2); } } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategyTest.java b/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategyTest.java new file mode 100644 index 00000000..283b306a --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategyTest.java @@ -0,0 +1,19 @@ +package edu.kit.datamanager.ro_crate.writer; + +import java.io.*; +import java.nio.file.Path; + +import edu.kit.datamanager.ro_crate.Crate; + +/** + * @author jejkal + */ +class ZipStreamStrategyTest extends CrateWriterTest { + + @Override + protected void saveCrate(Crate crate, Path target) throws IOException { + try (FileOutputStream stream = new FileOutputStream(target.toFile())) { + Writers.newZipStreamWriter().save(crate, stream); + } + } +} diff --git a/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipWriterTest.java b/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipWriterTest.java index 641de7af..3b8b1fc0 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipWriterTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipWriterTest.java @@ -1,171 +1,14 @@ package edu.kit.datamanager.ro_crate.writer; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; import java.nio.file.Path; -import edu.kit.datamanager.ro_crate.HelpFunctions; -import edu.kit.datamanager.ro_crate.RoCrate; -import edu.kit.datamanager.ro_crate.entities.data.DataSetEntity; -import edu.kit.datamanager.ro_crate.entities.data.FileEntity; -import edu.kit.datamanager.ro_crate.preview.AutomaticPreview; -import edu.kit.datamanager.ro_crate.preview.PreviewGenerator; -import net.lingala.zip4j.ZipFile; -import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -/** - * @author Nikola Tzotchev on 9.2.2022 г. - * @version 1 - */ -class ZipWriterTest { - - @Test - void testWritingToZip(@TempDir Path tempDir) throws IOException { - // create the RO_crate directory in the tempDir - Path roDir = tempDir.resolve("ro_dir"); - FileUtils.forceMkdir(roDir.toFile()); - - // the .json of our crate - InputStream fileJson= - ZipWriterTest.class.getResourceAsStream("/json/crate/fileAndDir.json"); - - // fill the expected directory with files and dirs - - Path json = roDir.resolve("ro-crate-metadata.json"); - FileUtils.copyInputStreamToFile(fileJson, json.toFile()); - - PreviewGenerator.generatePreview(roDir.toString()); - - Path file1 = roDir.resolve("cp7glop.ai"); - FileUtils.writeStringToFile(file1.toFile(), "content of Local File", Charset.defaultCharset()); - Path dirInCrate = roDir.resolve("dir"); - FileUtils.forceMkdir(dirInCrate.toFile()); - FileUtils.writeStringToFile(dirInCrate.resolve("first.txt").toFile(), - "content of first file in dir", Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInCrate.resolve("second.txt").toFile(), - "content of second file in dir", - Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInCrate.resolve("third.txt").toFile(), - "content of third file in dir", - Charset.defaultCharset()); - - // create the RO_Crate including the files that should be present in it - RoCrate roCrate = new RoCrate.RoCrateBuilder("Example RO-Crate", - "The RO-Crate Root Data Entity", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Diagram showing trend to increase") - .addProperty("contentSize", "383766") - .addProperty("description", "Illustrator file for Glop Pot") - .setEncodingFormat("application/pdf") - .setLocationWithExceptions(file1) - .setId("cp7glop.ai") - .build() - ) - .addDataEntity( - new DataSetEntity.DataSetBuilder() - .addProperty("name", "Too many files") - .addProperty("description", - "This directory contains many small files, that we're not going to describe in detail.") - .setLocationWithExceptions(dirInCrate) - .setId("lots_of_little_files/") - .build() - ) - .setPreview(new AutomaticPreview()) - .build(); - - // safe the crate in the test.zip file - Path test = tempDir.resolve("test.zip"); - // create a Writer for writing RoCrates to zip - RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); - // save the content of the roCrate to the dest zip - roCrateZipWriter.save(roCrate, test.toString()); - Path res = tempDir.resolve("dest"); - try (ZipFile zf = new ZipFile(test.toFile())) { - zf.extractAll(res.toString()); - } - assertTrue(HelpFunctions.compareTwoDir(roDir.toFile(), res.toFile())); - - // just so we know the metadata is still valid - HelpFunctions.compareCrateJsonToFileInResources(roCrate, "/json/crate/fileAndDir.json"); - } - - - @Test - void testWritingToZipFail(@TempDir Path tempDir) throws IOException { - // create the RO_crate directory in the tempDir - Path roDir = tempDir.resolve("ro_dir"); - FileUtils.forceMkdir(roDir.toFile()); - - // the .json of our crate - InputStream fileJson= - ZipWriterTest.class.getResourceAsStream("/json/crate/fileAndDir.json"); +import edu.kit.datamanager.ro_crate.Crate; - // fill the expected directory with files and dirs - - Path json = roDir.resolve("ro-crate-metadata.json"); - FileUtils.copyInputStreamToFile(fileJson, json.toFile()); - - PreviewGenerator.generatePreview(roDir.toFile().getAbsolutePath()); - - Path file1 = roDir.resolve("input.txt"); - FileUtils.writeStringToFile(file1.toFile(), "content of Local File", Charset.defaultCharset()); - Path dirInCrate = roDir.resolve("dir"); - FileUtils.forceMkdir(dirInCrate.toFile()); - FileUtils.writeStringToFile(dirInCrate.resolve("first.txt").toFile(), - "content of first file in dir", Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInCrate.resolve("second.txt").toFile(), - "content of second file in dir", - Charset.defaultCharset()); - FileUtils.writeStringToFile(dirInCrate.resolve("third.txt").toFile(), - "content of third file in dir", - Charset.defaultCharset()); - // false file, this test case should fal - Path falseFile = tempDir.resolve("new"); - FileUtils.writeStringToFile(falseFile.toFile(), "this file contains something else", Charset.defaultCharset()); - // create the RO_Crate including the files that should be present in it - RoCrate roCrate = new RoCrate.RoCrateBuilder("Example RO-Crate", - "The RO-Crate Root Data Entity", "2024", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") - .addDataEntity( - new FileEntity.FileEntityBuilder() - .addProperty("name", "Diagram showing trend to increase") - .addProperty("contentSize", "383766") - .addProperty("description", "Illustrator file for Glop Pot") - .setEncodingFormat("application/pdf") - .setLocationWithExceptions(falseFile) - .setId("cp7glop.ai") - .build() - ) - .addDataEntity( - new DataSetEntity.DataSetBuilder() - .addProperty("name", "Too many files") - .addProperty("description", - "This directory contains many small files, that we're not going to describe in detail.") - .setLocationWithExceptions(dirInCrate) - .setId("lots_of_little_files/") - .build() - ) - .build(); - - // safe the crate in the test.zip file - Path test = tempDir.resolve("test.zip"); - // create a Writer for writing RoCrates to zip - RoCrateWriter roCrateZipWriter = new RoCrateWriter(new ZipWriter()); - // save the content of the roCrate to the dest zip - roCrateZipWriter.save(roCrate, test.toFile().getAbsolutePath()); - Path res = tempDir.resolve("dest"); - try (ZipFile zf = new ZipFile(test.toFile())) { - zf.extractAll(res.toFile().getAbsolutePath()); +class ZipWriterTest extends CrateWriterTest { + @Override + protected void saveCrate(Crate crate, Path target) throws IOException { + Writers.newZipPathWriter() + .save(crate, target.toAbsolutePath().toString()); } - assertFalse(HelpFunctions.compareTwoDir(roDir.toFile(), res.toFile())); - - // just so we know the metadata is still valid - HelpFunctions.compareCrateJsonToFileInResources(roCrate, "/json/crate/fileAndDir.json"); - } } diff --git a/src/test/resources/crates/extendedContextExample/ro-crate-metadata.json b/src/test/resources/crates/extendedContextExample/ro-crate-metadata.json new file mode 100644 index 00000000..c9648594 --- /dev/null +++ b/src/test/resources/crates/extendedContextExample/ro-crate-metadata.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "owl": "https://www.w3.org/TR/owl-ref/#", + "datacite": "http://datacite.org/schema/kernel-4#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "rdfs": "https://www.w3.org/2000/01/rdf-schema#", + "custom": "_:" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "name" : "minimal", + "description" : "minimal RO_crate", + "datePublished": "2024", + "license": {"@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/"} + }, + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, + "about": {"@id": "./"} + } + ] +} \ No newline at end of file diff --git a/src/test/resources/json/entities/contextual/contactPoint.json b/src/test/resources/json/entities/contextual/contactPoint.json deleted file mode 100644 index 457e18f9..00000000 --- a/src/test/resources/json/entities/contextual/contactPoint.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@id": "mailto:tim.luckett@uts.edu.au", - "@type": "ContactPoint", - "contactType": "customer service", - "email": "tim.luckett@uts.edu.au", - "telephone": "0999444" -} \ No newline at end of file diff --git a/src/test/resources/json/entities/contextual/geo.json b/src/test/resources/json/entities/contextual/geo.json deleted file mode 100644 index 6ac07e05..00000000 --- a/src/test/resources/json/entities/contextual/geo.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@id": "#b4168a98-8534-4c6d-a568-64a55157b656", - "@type": "GeoCoordinates", - "latitude": "-33.7152", - "longitude": "150.30119", - "name": "latitude: -33.7152 longitude: 150.30119", - "address": "this land nomero 2", - "postalcode": "76588", - "elevation": "100" -} \ No newline at end of file diff --git a/src/test/resources/json/entities/contextual/rorkit.json b/src/test/resources/json/entities/contextual/rorkit.json deleted file mode 100644 index c29ceb2a..00000000 --- a/src/test/resources/json/entities/contextual/rorkit.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "@id" : "https://ror.org/04t3en479", - "@type" : "Organization", - "name" : "Karlsruhe Institute of Technology", - "url" : "https://www.kit.edu" -} \ No newline at end of file