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..35fe0f66 --- /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.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) { + if (metadataJson == null) { + logger.error("Metadata JSON is null, cannot rebuild crate"); + return null; + } + if (files == null) { + logger.error("Content files directory is null, cannot rebuild crate"); + return null; + } + 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/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 087888a5..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,29 +1,5 @@ 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.io.InputStream; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * This class allows reading crates from the outside into the library in order * to inspect or modify it. @@ -33,333 +9,12 @@ *

* 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 { - - private static Logger logger = LoggerFactory.getLogger(RoCrateReader.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 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 source the input stream to read from - * - * @return the read RO-crate - */ - public RoCrate readCrate(InputStream source) { - RoCrate result = null; - if (reader instanceof StreamReaderStrategy streamReaderStrategy) { - ObjectNode metadata = streamReaderStrategy.readMetadataJson(source); - File content = streamReaderStrategy.readContent(source); - HashSet usedFiles = new HashSet<>(); - usedFiles.add(content.toPath().resolve(FILE_METADATA_JSON).toFile().getPath()); - usedFiles.add(content.toPath().resolve(FILE_PREVIEW_HTML).toFile().getPath()); - usedFiles.add(content.toPath().resolve(FILE_PREVIEW_FILES).toFile().getPath()); - result = rebuildCrate(metadata, content, usedFiles); - } else { - logger.error("Provided writer does not implement StreamReaderStrategy. Please use 'readCrate(String location)'."); - } - return result; - } - - /** - * 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(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) { - if (metadataJson == null) { - logger.error("Metadata JSON is null, cannot rebuild crate"); - return null; - } - if (files == null) { - logger.error("Content files directory is null, cannot rebuild crate"); - return null; - } - 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); +@Deprecated(since = "2.1.0", forRemoval = true) +public class RoCrateReader extends CrateReader { + public RoCrateReader(GenericReaderStrategy reader) { + super(reader); } } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/reader/StreamReaderStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/StreamReaderStrategy.java deleted file mode 100644 index 3dfcc243..00000000 --- a/src/main/java/edu/kit/datamanager/ro_crate/reader/StreamReaderStrategy.java +++ /dev/null @@ -1,64 +0,0 @@ -package edu.kit.datamanager.ro_crate.reader; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import edu.kit.datamanager.ro_crate.writer.StreamWriterStrategy; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import org.slf4j.LoggerFactory; - -/** - * Interface for reading RO-Crate metadata and content from input streams. - * - * @author jejkal - */ -public interface StreamReaderStrategy extends ReaderStrategy { - - org.slf4j.Logger logger = LoggerFactory.getLogger(StreamReaderStrategy.class); - - /** - * Default override of readMetadataJson interface from ReaderStrategy. The - * override assumes, that location is a file, which is used as input stream. - * If this assumption is not true, this call will fail. - * - * @param location The source, which is supposed to be a file. - * - * @return the RO-Crate metadata as ObjectNode - */ - @Override - default ObjectNode readMetadataJson(String location) { - ObjectNode result = null; - try { - result = readMetadataJson(new FileInputStream(new File(location))); - } catch (FileNotFoundException ex) { - logger.error("Failed read crate from source " + location, ex); - } - return result; - } - - /** - * Default override of readContent interface from ReaderStrategy. The - * override assumes, that location is a file, which is used as input stream. - * If this assumption is not true, this call will fail. - * - * @param location The source, which is supposed to be a file. - * - * @return the RO-Crate content as file, i.e., a folder - */ - @Override - default File readContent(String location) { - File result = null; - try { - result = readContent(new FileInputStream(new File(location))); - } catch (FileNotFoundException ex) { - logger.error("Failed read crate from source " + location, ex); - } - return result; - } - - ObjectNode readMetadataJson(InputStream source); - - File readContent(InputStream source); - -} 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/ZipStreamReader.java b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamStrategy.java similarity index 93% rename from src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamReader.java rename to src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamStrategy.java index b71212bb..cb4f53af 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamReader.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/reader/ZipStreamStrategy.java @@ -13,6 +13,8 @@ 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. @@ -21,8 +23,9 @@ * * @author jejkal */ -public class ZipStreamReader implements StreamReaderStrategy { +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; @@ -31,7 +34,7 @@ public class ZipStreamReader implements StreamReaderStrategy { * Crates a ZipStreamReader with the default configuration as described in * the class documentation. */ - public ZipStreamReader() { + public ZipStreamStrategy() { } /** @@ -44,7 +47,7 @@ public ZipStreamReader() { * subdirectories of the given directory. These subdirectories will have * UUIDs as their names. */ - public ZipStreamReader(Path folderPath, boolean shallAddUuidSubfolder) { + public ZipStreamStrategy(Path folderPath, boolean shallAddUuidSubfolder) { if (shallAddUuidSubfolder) { this.temporaryFolder = folderPath.resolve(ID); } else { 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 ab704181..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,64 +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; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * 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 { - - private static Logger logger = LoggerFactory.getLogger(FolderWriter.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); - } - } - } -} +@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 112b4d3a..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,51 +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; -import java.io.OutputStream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * 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 static Logger logger = LoggerFactory.getLogger(RoCrateWriter.class); - - private final WriterStrategy writer; - - public RoCrateWriter(WriterStrategy writer) { - this.writer = writer; - } - - /** - * 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); - } +@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, OutputStream destination) { - Validator defaultValidation = new Validator(new JsonSchemaValidation()); - defaultValidation.validate(crate); - if (writer instanceof StreamWriterStrategy streamWriterStrategy) { - streamWriterStrategy.save(crate, destination); - } else { - logger.error("Provided writer does not implement StreamWriterStrategy. Please use 'save(Crate crate, String destination)'."); - } + public RoCrateWriter(GenericWriterStrategy writer) { + super(writer); } } diff --git a/src/main/java/edu/kit/datamanager/ro_crate/writer/StreamWriterStrategy.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/StreamWriterStrategy.java deleted file mode 100644 index 04deb659..00000000 --- a/src/main/java/edu/kit/datamanager/ro_crate/writer/StreamWriterStrategy.java +++ /dev/null @@ -1,36 +0,0 @@ -package edu.kit.datamanager.ro_crate.writer; - -import edu.kit.datamanager.ro_crate.Crate; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.OutputStream; -import org.slf4j.LoggerFactory; - -/** - * Strategy for writing of crates to streams. - * - * @author jejkal - */ -public interface StreamWriterStrategy extends WriterStrategy { - - static org.slf4j.Logger logger = LoggerFactory.getLogger(StreamWriterStrategy.class); - - /** - * Default override of save interface from WriterStrategy. The override - * assumes, that destination is a file, which is used as output stream. If - * this assumption is not true, this call will fail. - * - * @param crate The crate to write. - * @param destination The destination, which is supposed to be a file. - */ - default void save(Crate crate, String destination) { - try { - save(crate, new FileOutputStream(new File(destination))); - } catch (FileNotFoundException ex) { - logger.error("Failed save crate to destination " + destination, ex); - } - } - - void save(Crate crate, OutputStream destination); -} 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..c944aad3 --- /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) { + 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 (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/ZipStreamWriter.java b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategy.java similarity index 91% rename from src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamWriter.java rename to src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategy.java index 3145c3a1..818c064c 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamWriter.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategy.java @@ -21,9 +21,9 @@ * Implementation of the writing strategy to provide a way of writing crates to * a zip archive. */ -public class ZipStreamWriter implements StreamWriterStrategy { +public class ZipStreamStrategy implements GenericWriterStrategy { - private static Logger logger = LoggerFactory.getLogger(ZipStreamWriter.class); + private static final Logger logger = LoggerFactory.getLogger(ZipStreamStrategy.class); @Override public void save(Crate crate, OutputStream destination) { @@ -41,7 +41,7 @@ private void saveDataEntities(Crate crate, ZipOutputStream zipStream) { try { dataEntity.saveToStream(zipStream); } catch (IOException e) { - logger.error("Could not save " + dataEntity.getId() + " to zip stream!", e); + logger.error("Could not save {} to zip stream!", dataEntity.getId(), 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 dff25fd0..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,69 +1,10 @@ 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.nio.charset.StandardCharsets; -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; - /** * 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 { - - private static Logger logger = LoggerFactory.getLogger(ZipWriter.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) { - 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 (IOException e) { - logger.error("Exception writing ro-crate-metadata.json file to zip.", e); - } - } -} +@Deprecated(since = "2.1.0", forRemoval = true) +public class ZipWriter extends ZipStrategy {} 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/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/ReadAndWriteTest.java b/src/test/java/edu/kit/datamanager/ro_crate/crate/ReadAndWriteTest.java index 2f2e308c..4a39cca3 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,10 +3,9 @@ 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.preview.StaticPreview; -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 edu.kit.datamanager.ro_crate.writer.FolderWriter; import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; @@ -40,7 +39,7 @@ void testReadingAndWriting(@TempDir Path path) throws IOException { RoCrateWriter writer = new RoCrateWriter(new FolderWriter()); writer.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 @@ -51,7 +50,7 @@ void testReadingAndWriting(@TempDir Path path) throws IOException { @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 8f008510..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,7 +1,6 @@ package edu.kit.datamanager.ro_crate.crate; -import edu.kit.datamanager.ro_crate.reader.FolderReader; -import edu.kit.datamanager.ro_crate.reader.RoCrateReader; +import edu.kit.datamanager.ro_crate.reader.Readers; import edu.kit.datamanager.ro_crate.HelpFunctions; import edu.kit.datamanager.ro_crate.RoCrate; @@ -21,7 +20,7 @@ public class TestRemoveAddContext { void setup() { String crateManifestPath = "/crates/extendedContextExample/"; crateManifestPath = Objects.requireNonNull(TestRemoveAddContext.class.getResource(crateManifestPath)).getPath(); - this.crateWithComplexContext = new RoCrateReader(new FolderReader()).readCrate(crateManifestPath); + this.crateWithComplexContext = Readers.newFolderReader().readCrate(crateManifestPath); } @Test 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..1d45bd09 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,8 +2,8 @@ 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.reader.CrateReader; +import edu.kit.datamanager.ro_crate.reader.Readers; import edu.kit.datamanager.ro_crate.writer.FolderWriter; import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; @@ -18,7 +18,7 @@ public class WorkflowHubTest { @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"); 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 d4eb91fb..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,149 +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() - ) - .setPreview(null)//disable preview to allow to compare folders before and after - .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 33006ff2..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,200 +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()); +class ZipReaderTest extends CrateReaderTest { - 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); + @Override + protected void saveCrate(Crate crate, Path target) { + Writers.newZipPathWriter().save(crate, target.toAbsolutePath().toString()); + assertTrue(target.toFile().isFile()); } - @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() - ) - .setPreview(null)//disable preview to allow to compare folders before and after - .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()); - - // that copies the directory locally to see its content - assertTrue(HelpFunctions.compareTwoDir(locationSource.toFile(), destinationDir.toFile())); - HelpFunctions.compareTwoCrateJson(roCrate, res); + @Override + protected Crate readCrate(Path source) throws IOException { + return Readers.newZipPathReader().readCrate(source.toAbsolutePath().toString()); } - @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); + @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; } - @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)); - - RoCrateReader roCrateFolderReader = new RoCrateReader(readerType); - Crate crate = roCrateFolderReader.readCrate(zipFile.getAbsolutePath()); - assertTrue(readerType.isExtracted()); - HelpFunctions.compareTwoCrateJson(roCrate, crate); - - { - // 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)); - - 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 index 05ca63d5..33436c6c 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipStreamReaderTest.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/reader/ZipStreamReaderTest.java @@ -1,201 +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.FileInputStream; -import java.io.IOException; -import java.nio.charset.Charset; +import java.io.*; import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; -class ZipStreamReaderTest { - - @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 roCrateReader = new RoCrateReader(new ZipStreamReader()); - Crate res = roCrateReader.readCrate(new FileInputStream(zipFile)); - 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 ZipStreamReader()); - Crate res = roCrateFolderReader.readCrate(new FileInputStream(zipPath.toFile())); - - HelpFunctions.compareTwoCrateJson(roCrate, res); +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()); } - @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() - ) - .setPreview(null)//disable preview to allow to compare folders before and after - .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 ZipStreamReader()); - Crate res = roCrateFolderReader.readCrate(new FileInputStream(zipPath.toFile())); - - 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()); - - // that copies the directory locally to see its content - assertTrue(HelpFunctions.compareTwoDir(locationSource.toFile(), destinationDir.toFile())); - HelpFunctions.compareTwoCrateJson(roCrate, res); + @Override + protected Crate readCrate(Path source) throws IOException { + return Readers.newZipStreamReader().readCrate(new FileInputStream(source.toFile())); } - @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 ZipStreamReader()); - Crate res = roCrateFolderReader.readCrate(new FileInputStream(zipPath.toFile())); - - 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); + @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; } - @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"); - ZipStreamReader readerType = new ZipStreamReader(differentFolder, true); - assertFalse(readerType.isExtracted()); - assertEquals(readerType.getTemporaryFolder().getFileName().toString(), readerType.getID()); - assertTrue(readerType.getTemporaryFolder().startsWith(differentFolder)); - - RoCrateReader roCrateFolderReader = new RoCrateReader(readerType); - Crate crate = roCrateFolderReader.readCrate(new FileInputStream(zipFile)); - assertTrue(readerType.isExtracted()); - HelpFunctions.compareTwoCrateJson(roCrate, crate); - - { - // try it again without the UUID subfolder and test if the directory is being cleaned up (using coverage). - ZipStreamReader newReaderType = new ZipStreamReader(differentFolder, false); - assertFalse(newReaderType.isExtracted()); - assertNotEquals(newReaderType.getTemporaryFolder().getFileName().toString(), newReaderType.getID()); - assertTrue(newReaderType.getTemporaryFolder().startsWith(differentFolder)); - - RoCrateReader newRoCrateFolderReader = new RoCrateReader(newReaderType); - Crate crate2 = newRoCrateFolderReader.readCrate(new FileInputStream(zipFile)); - assertTrue(newReaderType.isExtracted()); - HelpFunctions.compareTwoCrateJson(roCrate, crate2); - } + @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..0e6e51e0 --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/writer/CrateWriterTest.java @@ -0,0 +1,280 @@ +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).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" + ); + } + + /** + * 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 ba6ecc1a..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,214 +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.CustomPreview; -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()); +class FolderWriterTest extends CrateWriterTest { - // 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()))); + @Override + protected void saveCrate(Crate crate, Path target) throws IOException { + Writers.newFolderWriter() + .save(crate, target.toAbsolutePath().toString()); } - - @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()); - - //create preview via rochtml or custom as fallback - if (PreviewGenerator.isRochtmlAvailable()) { - PreviewGenerator.generatePreview(roDir.toString()); - } else { - new CustomPreview().saveAllToFolder(roDir.toFile()); - } - 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"); + @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..2ba74518 --- /dev/null +++ b/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategyTest.java @@ -0,0 +1,18 @@ +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 { + FileOutputStream stream = new FileOutputStream(target.toFile()); + Writers.newZipStreamWriter().save(crate, stream); + } +} diff --git a/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipStreamWriterTest.java b/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipStreamWriterTest.java deleted file mode 100644 index 2aa39bf3..00000000 --- a/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipStreamWriterTest.java +++ /dev/null @@ -1,172 +0,0 @@ -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 jejkal - */ -class ZipStreamWriterTest { - - @Test - void testWritingToZipStream(@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= - ZipStreamWriterTest.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.toFile().getAbsolutePath()); - - 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 ZipStreamWriter()); - - - // 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()); - } - 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= - ZipStreamWriterTest.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.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() - ) - .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 ZipStreamWriter()); - // 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()); - } - 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/java/edu/kit/datamanager/ro_crate/writer/ZipWriterTest.java b/src/test/java/edu/kit/datamanager/ro_crate/writer/ZipWriterTest.java index a3a2e231..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,175 +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.CustomPreview; -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()); - - //create preview via rochtml or custom as fallback - if (PreviewGenerator.isRochtmlAvailable()) { - PreviewGenerator.generatePreview(roDir.toString()); - } else { - new CustomPreview().saveAllToFolder(roDir.toFile()); - } - 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()); - roCrateZipWriter.save(roCrate, "/Users/jejkal/cra.zip"); - - 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"); - - // 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()); - } - assertFalse(HelpFunctions.compareTwoDir(roDir.toFile(), res.toFile())); +import edu.kit.datamanager.ro_crate.Crate; - // just so we know the metadata is still valid - HelpFunctions.compareCrateJsonToFileInResources(roCrate, "/json/crate/fileAndDir.json"); +class ZipWriterTest extends CrateWriterTest { + @Override + protected void saveCrate(Crate crate, Path target) throws IOException { + Writers.newZipPathWriter() + .save(crate, target.toAbsolutePath().toString()); } }