From 7f59d79c2a29df70ea357a5a080afce1b80ab1b9 Mon Sep 17 00:00:00 2001 From: Juan Fuentes Date: Tue, 5 Aug 2025 16:05:45 +0200 Subject: [PATCH] First commit, specification moved from https://sissource.ethz.ch/sispub/ro-crate/ --- .../reference-openbis-export/metadata.xlsx | Bin 0 -> 10837 bytes 0.1.x/examples/ro-crate-1.1/dist/rocrate.zip | Bin 0 -> 9992 bytes .../ro-crate-metadata/ro-crate-metadata.json | 581 ++++++++ 0.1.x/lib/java/bin/lib-ro-crate.jar | Bin 0 -> 25450 bytes 0.1.x/lib/java/src/build.gradle | 74 + .../java/ch/eth/sis/rocrate/SchemaFacade.java | 258 ++++ .../eth/sis/rocrate/example/ReadExample.java | 31 + .../eth/sis/rocrate/example/WriteExample.java | 58 + .../ch/eth/sis/rocrate/facade/IDataType.java | 21 + .../sis/rocrate/facade/IMetadataEntry.java | 29 + .../eth/sis/rocrate/facade/IPropertyType.java | 19 + .../eth/sis/rocrate/facade/ISchemaFacade.java | 35 + .../java/ch/eth/sis/rocrate/facade/IType.java | 18 + .../eth/sis/rocrate/facade/LiteralType.java | 32 + .../eth/sis/rocrate/facade/MetadataEntry.java | 103 ++ .../ch/eth/sis/rocrate/facade/RdfsClass.java | 86 ++ .../eth/sis/rocrate/facade/TypeProperty.java | 81 ++ 0.1.x/spec.md | 300 ++++ .../reference-openbis-export/metadata.xlsx | Bin 0 -> 11065 bytes 0.2.x/examples/ro-crate-1.1/dist/rocrate.zip | Bin 0 -> 9992 bytes .../ro-crate-metadata/ro-crate-metadata.json | 1218 +++++++++++++++++ 0.2.x/lib/java/bin/lib-ro-crate.jar | Bin 0 -> 24531 bytes 0.2.x/lib/java/src/build.gradle | 74 + .../ch/ethz/sis/rocrate/SchemaFacade.java | 566 ++++++++ .../ethz/sis/rocrate/example/ReadExample.java | 38 + .../sis/rocrate/example/WriteExample.java | 108 ++ .../ch/ethz/sis/rocrate/facade/IDataType.java | 21 + .../sis/rocrate/facade/IMetadataEntry.java | 30 + .../sis/rocrate/facade/IPropertyType.java | 25 + .../ethz/sis/rocrate/facade/IRestriction.java | 13 + .../sis/rocrate/facade/ISchemaFacade.java | 45 + .../ch/ethz/sis/rocrate/facade/IType.java | 27 + .../ethz/sis/rocrate/facade/LiteralType.java | 58 + .../sis/rocrate/facade/MetadataEntry.java | 118 ++ .../ethz/sis/rocrate/facade/PropertyType.java | 132 ++ .../ch/ethz/sis/rocrate/facade/RdfsClass.java | 86 ++ .../ethz/sis/rocrate/facade/Restriction.java | 48 + .../java/ch/ethz/sis/rocrate/facade/Type.java | 144 ++ 0.2.x/spec.md | 409 ++++++ README.md | 40 + 40 files changed, 4926 insertions(+) create mode 100644 0.1.x/examples/reference-openbis-export/metadata.xlsx create mode 100644 0.1.x/examples/ro-crate-1.1/dist/rocrate.zip create mode 100644 0.1.x/examples/ro-crate-1.1/ro-crate-metadata/ro-crate-metadata.json create mode 100644 0.1.x/lib/java/bin/lib-ro-crate.jar create mode 100644 0.1.x/lib/java/src/build.gradle create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/SchemaFacade.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/ReadExample.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/WriteExample.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IDataType.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IMetadataEntry.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IPropertyType.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/ISchemaFacade.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IType.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/LiteralType.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/MetadataEntry.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/RdfsClass.java create mode 100644 0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/TypeProperty.java create mode 100644 0.1.x/spec.md create mode 100644 0.2.x/examples/reference-openbis-export/metadata.xlsx create mode 100644 0.2.x/examples/ro-crate-1.1/dist/rocrate.zip create mode 100644 0.2.x/examples/ro-crate-1.1/ro-crate-metadata/ro-crate-metadata.json create mode 100644 0.2.x/lib/java/bin/lib-ro-crate.jar create mode 100644 0.2.x/lib/java/src/build.gradle create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/SchemaFacade.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/ReadExample.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/WriteExample.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IDataType.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IMetadataEntry.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IPropertyType.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IRestriction.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/ISchemaFacade.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IType.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/LiteralType.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/MetadataEntry.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/PropertyType.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/RdfsClass.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Restriction.java create mode 100644 0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Type.java create mode 100644 0.2.x/spec.md create mode 100644 README.md diff --git a/0.1.x/examples/reference-openbis-export/metadata.xlsx b/0.1.x/examples/reference-openbis-export/metadata.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5ef8f851cc5f0762cdf3f3ac5db7d66442676694 GIT binary patch literal 10837 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 literal 0 HcmV?d00001 diff --git a/0.1.x/examples/ro-crate-1.1/dist/rocrate.zip b/0.1.x/examples/ro-crate-1.1/dist/rocrate.zip new file mode 100644 index 0000000000000000000000000000000000000000..498d3c9a1a7ac566dce15b522f2250838b05d6b3 GIT binary patch literal 9992 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 + + // https://docs.gradle.org/current/userguide/resolution_rules.html + if (details.requested.group == 'com.fasterxml.jackson.core' && details.requested.name == 'jackson-databind') { + details.useVersion '2.18.0' + } + if (details.requested.group == 'com.fasterxml.jackson.core' && details.requested.name == 'jackson-core') { + details.useVersion '2.18.0' + } + if (details.requested.group == 'org.slf4j') { + details.useVersion '2.0.16' + } + } +} + + +tasks.register('buildRdfTool', Jar) { + dependsOn tasks.build + duplicatesStrategy 'include' + + archiveBaseName.set('lib-rdf-tool') + includeEmptyDirs = false + + manifest { + attributes( + 'Main-Class': 'ch.ethz.sis.rdf.main.RDFCommandLine' + ) + } + + from(zipTree(tasks.jar.outputs.files.singleFile)) { + include '**/*' + } + + from(configurations.runtimeClasspath.collect { zipTree(it) }) { + include '**/*' + exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA' + } +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/SchemaFacade.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/SchemaFacade.java new file mode 100644 index 0000000..41dbcc3 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/SchemaFacade.java @@ -0,0 +1,258 @@ +package ch.eth.sis.rocrate; + +import ch.eth.sis.rocrate.facade.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; + +import java.io.Serializable; +import java.util.*; + +public class SchemaFacade implements ISchemaFacade +{ + + private final static String RDFS_CLASS = "rdfs:Class"; + + private final static String RDFS_PROPERTY = "rdfs:Property"; + + public static final String EQUIVALENT_CLASS = "owl:equivalentClass"; + + public static final String EQUIVALENT_CONCEPT = "owl:equivalentProperty"; + + + private Map types; + + private Map propertyTypes; + + private Map metadataEntries; + + private final RoCrate crate; + + public SchemaFacade(RoCrate crate) + { + this.crate = crate; + this.types = new LinkedHashMap<>(); + this.propertyTypes = new LinkedHashMap<>(); + this.metadataEntries = new LinkedHashMap<>(); + } + + public static SchemaFacade of(RoCrate crate) throws JsonProcessingException + { + SchemaFacade schemaFacade = new SchemaFacade(crate); + schemaFacade.parseEntities(); + return schemaFacade; + + } + + @Override + public void addType(IType rdfsClass) + { + + DataEntity.DataEntityBuilder builder = new DataEntity.DataEntityBuilder(); + builder.addProperty("@id", rdfsClass.getId()); + builder.addProperty("@type", RDFS_CLASS); + rdfsClass.getSubClassOf().forEach(x -> builder.addIdProperty("rdfs:subClassOf", x)); + this.types.put(rdfsClass.getId(), rdfsClass); + DataEntity entity = builder.build(); + entity.addIdListProperties(EQUIVALENT_CLASS, rdfsClass.getOntologicalAnnotations()); + crate.addDataEntity(entity); + + } + + @Override + public List getTypes() + { + return this.types.values().stream().toList(); + } + + @Override + public IType getTypes(String id) + { + return this.types.get(id); + } + + @Override + public void addPropertyType(IPropertyType rdfsProperty) + { + DataEntity.DataEntityBuilder builder = new DataEntity.DataEntityBuilder(); + + builder.setId(rdfsProperty.getId()); + builder.addProperty("@type", RDFS_PROPERTY); + + var stuff = builder.build(); + stuff.addIdListProperties("schema:rangeIncludes", + rdfsProperty.getRange()); + stuff.addIdListProperties("schema:domainIncludes", + rdfsProperty.getDomain()); + stuff.addIdListProperties(EQUIVALENT_CONCEPT, + rdfsProperty.getOntologicalAnnotations()); + crate.addDataEntity(stuff); + propertyTypes.put(rdfsProperty.getId(), rdfsProperty); + + } + + @Override + public List getPropertyTypes() + { + return propertyTypes.values().stream().toList(); + } + + @Override + public IPropertyType getPropertyType(String id) + { + return propertyTypes.get(id); + } + + @Override + public void addEntry(IMetadataEntry metaDataEntry) + { + DataEntity.DataEntityBuilder builder = new DataEntity.DataEntityBuilder(); + builder.setId(metaDataEntry.getId()); + builder.addProperty("@type", metaDataEntry.getClassId()); + ObjectMapper objectMapper = new ObjectMapper(); + + metaDataEntry.getValues().forEach((s, o) -> { + if (o instanceof Double) + { + builder.addProperty(s, (Double) o); + } else if (o instanceof Integer) + { + builder.addProperty(s, (Integer) o); + } else if (o instanceof Boolean) + { + builder.addProperty(s, (Boolean) o); + } else if (o instanceof String) + { + builder.addProperty(s, o.toString()); + } else if (o == null) + { + builder.addProperty(s, objectMapper.nullNode()); + } + }); + DataEntity dataEntity = builder.build(); + metaDataEntry.getReferences().forEach(dataEntity::addIdListProperties); + + crate.addDataEntity(dataEntity); + + } + + @Override + public IMetadataEntry getEntry(String id) + { + return metadataEntries.get(id); + } + + @Override + public List getEntries(String rdfsClassId) + { + return metadataEntries.values().stream().toList(); + } + + private void parseEntities() throws JsonProcessingException + { + Map properties = new LinkedHashMap<>(); + Map classes = new LinkedHashMap<>(); + Map entries = new LinkedHashMap<>(); + + for (DataEntity entity : crate.getAllDataEntities()) + { + String type = entity + .getProperty("@type").asText(); + String id = + entity.getProperty("@id") + .asText(); + + switch (type) + { + case "rdfs:Class" -> + { + RdfsClass rdfsClass = new RdfsClass(); + rdfsClass.setSubClassOf(parseMultiValued(entity, "rdfs:subClassOf")); + rdfsClass.setOntologicalAnnotations( + parseMultiValued(entity, EQUIVALENT_CLASS)); + rdfsClass.setId(id); + classes.put(id, rdfsClass); + + } + case "rdfs:Property" -> + { + TypeProperty rdfsProperty = new TypeProperty(); + rdfsProperty.setId(id); + rdfsProperty.setOntologicalAnnotations( + parseMultiValued(entity, EQUIVALENT_CONCEPT)); + rdfsProperty.setRangeIncludes( + parseMultiValued(entity, "schema:rangeIncludes")); + rdfsProperty.setDomainIncludes( + parseMultiValued(entity, "schema:domainIncludes")); + properties.put(id, rdfsProperty); + + } + + } + + } + + for (var entity : crate.getAllDataEntities()) + { + String type = entity + .getProperty("@type").asText(); + String id = + entity.getProperty("@id") + .asText(); + if (!classes.containsKey(type)) + { + continue; + } + + Map entryProperties = new LinkedHashMap<>(); + MetadataEntry entry = new MetadataEntry(); + entry.setId(id); + entry.setType(type); + ObjectMapper objectMapper = new ObjectMapper(); + Map keyVals = + objectMapper.readValue(entity.getProperties().toString(), HashMap.class); + for (Map.Entry a : keyVals.entrySet()) + { + if (properties.containsKey(a.getKey())) + { + IPropertyType property = properties.get(a.getKey()); + if (property.getRange().stream().anyMatch(x -> x.equals("xsd:string"))) + { + entryProperties.put(a.getKey(), a.getValue().toString()); + } + } + } + entry.setProps(entryProperties); + entry.setReferences(new HashMap<>()); + entries.put(id, entry); + } + System.out.println("Done"); + this.types = classes; + this.propertyTypes = properties; + this.metadataEntries = entries; + + } + + private List parseMultiValued(DataEntity dataEntity, String key) + { + JsonNode node = dataEntity.getProperty(key); + if (node instanceof ObjectNode) + { + return List.of(node.get("@id").textValue()); + } + if (node instanceof ArrayNode arrayNode) + { + List accumulator = new ArrayList<>(); + arrayNode.elements().forEachRemaining( + x -> accumulator.add(x.get("@id").textValue()) + ); + return accumulator; + } + return List.of(); + + } +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/ReadExample.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/ReadExample.java new file mode 100644 index 0000000..3e70a2c --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/ReadExample.java @@ -0,0 +1,31 @@ +package ch.eth.sis.rocrate.example; + +import ch.eth.sis.rocrate.SchemaFacade; +import com.fasterxml.jackson.core.JsonProcessingException; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.reader.FolderReader; +import edu.kit.datamanager.ro_crate.reader.RoCrateReader; + +public class ReadExample +{ + + public static void main(String[] args) throws JsonProcessingException + { + String path = args.length >= 1 ? args[0] : "out"; + RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); + RoCrate crate = roCrateFolderReader.readCrate(path); + SchemaFacade schemaFacade = SchemaFacade.of(crate); + schemaFacade.getTypes().forEach( + x -> System.out.println("RDFS Class " + x.getId()) + ); + schemaFacade.getPropertyTypes().forEach( + x -> System.out.println("RDFS Property " + x.getId()) + ); + schemaFacade.getEntries(schemaFacade.getTypes().get(0).getId()).forEach( + x -> System.out.println("Metadata entry " + x.getId()) + ); + + } + + +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/WriteExample.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/WriteExample.java new file mode 100644 index 0000000..880affd --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/example/WriteExample.java @@ -0,0 +1,58 @@ +package ch.eth.sis.rocrate.example; + +import ch.eth.sis.rocrate.SchemaFacade; +import ch.eth.sis.rocrate.facade.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.writer.FolderWriter; + +import java.util.List; +import java.util.Map; + +public class WriteExample +{ + public static void main(String[] args) throws JsonProcessingException + { + RoCrate.RoCrateBuilder roCrateBuilder = + new RoCrate.RoCrateBuilder("name", "description", "2024-12-04T07:53:11Z", + "licenseIdentifier"); + ISchemaFacade schemaFacade = SchemaFacade.of(roCrateBuilder.build()); + + { + RdfsClass rdfsClass = new RdfsClass(); + rdfsClass.setId("TextResource"); + rdfsClass.setSubClassOf(List.of("https://schema.org/Thing")); + rdfsClass.setOntologicalAnnotations( + List.of("https://www.dublincore.org/specifications/dublin-core/dcmi-terms/dcmitype/Text/")); + schemaFacade.addType(rdfsClass); + + TypeProperty property = new TypeProperty(); + property.setId("hasDateSubmitted"); + property.setTypes(List.of(LiteralType.DATETIME)); + rdfsClass.addProperty(property); + + + property.setOntologicalAnnotations( + List.of("https://www.dublincore.org/specifications/dublin-core/dcmi-terms/terms/dateSubmitted/")); + schemaFacade.addPropertyType(property); + + } + { + IMetadataEntry metadataEntry = new MetadataEntry("TextResource1", "TextResource", + Map.of("hasDate", "2025-01-21T07:12:20Z"), Map.of()); + schemaFacade.addEntry(metadataEntry); + + } + + String path = args.length >= 1 ? args[0] : "out"; + + + + roCrateBuilder.build(); + + FolderWriter folderWriter = new FolderWriter(); + folderWriter.save(roCrateBuilder.build(), path); + + } + +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IDataType.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IDataType.java new file mode 100644 index 0000000..6c9eb38 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IDataType.java @@ -0,0 +1,21 @@ +package ch.eth.sis.rocrate.facade; + +public interface IDataType +{ + String getTypeName(); + + static IDataType getArray(LiteralType literalType) + { //TODO static IDataType getArray(LiteralType literalType) + throw new UnsupportedOperationException("Feature incomplete. Contact assistance."); + } + + static IDataType getEnumerationf(String... values) + { //TODO static IDataType getEnumerationf(String... values) + throw new UnsupportedOperationException("Feature incomplete. Contact assistance."); + } + + static IDataType getCustomType(LiteralType basicType, String pattern) + { //TODO static IDataType getCustomType(LiteralType basicType, String pattern) + throw new UnsupportedOperationException("Feature incomplete. Contact assistance."); + } +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IMetadataEntry.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IMetadataEntry.java new file mode 100644 index 0000000..2bc4ff2 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IMetadataEntry.java @@ -0,0 +1,29 @@ +package ch.eth.sis.rocrate.facade; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +public interface IMetadataEntry +{ + + /** + * Returns the ID of this entry + */ + String getId(); + + /* Returns the type ID of this entry */ + String getClassId(); + + /* Returns the type of the entry */ + String getType(); + + /* These are key-value pairs for serialization. These are single-valued. + * Serializable classes are: String, Number and Boolean */ + Map getValues(); + + /* These are references to other objects in the graph. + * Each key may have one or more references */ + Map> getReferences(); + +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IPropertyType.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IPropertyType.java new file mode 100644 index 0000000..6c7eec1 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IPropertyType.java @@ -0,0 +1,19 @@ +package ch.eth.sis.rocrate.facade; + +import java.util.List; + +public interface IPropertyType +{ + /* Returns the ID of this property type */ + String getId(); + + /* Return possible values for the subject of this property type */ + List getDomain(); + + /* Return possible values for the object of this property type */ + List getRange(); + + /* Returns the ontological annotations of this property type */ + List getOntologicalAnnotations(); + +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/ISchemaFacade.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/ISchemaFacade.java new file mode 100644 index 0000000..2c6b8b9 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/ISchemaFacade.java @@ -0,0 +1,35 @@ +package ch.eth.sis.rocrate.facade; + +import java.util.List; + +public interface ISchemaFacade +{ + + /* Adds a single class */ + void addType(IType rdfsClass); + + /** Retrieves all Classes */ + List getTypes(); + + /* Get a single type by its ID */ + IType getTypes(String id); + + /* Adds a single property */ + void addPropertyType(IPropertyType property); + + /* Get all Properties */ + List getPropertyTypes(); + + /* Gets a single property by its ID. */ + IPropertyType getPropertyType(String id); + + /* Add a single metadata entry */ + void addEntry(IMetadataEntry entry); + + /* Get a single metadata entry by its ID */ + IMetadataEntry getEntry(String id); + + /* Get all metadata entities */ + List getEntries(String rdfsClassId); + +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IType.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IType.java new file mode 100644 index 0000000..f1d3c01 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/IType.java @@ -0,0 +1,18 @@ +package ch.eth.sis.rocrate.facade; + + +import java.util.List; + +public interface IType +{ + /* Returns the ID of this type */ + String getId(); + + /* Returns IDs of the types this type inherits from */ + List getSubClassOf(); + + /* Returns the ontological annotations of this type */ + List getOntologicalAnnotations(); + + +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/LiteralType.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/LiteralType.java new file mode 100644 index 0000000..99a5174 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/LiteralType.java @@ -0,0 +1,32 @@ +package ch.eth.sis.rocrate.facade; + +/** + * List of primitives as supported by xsd + * https://www.ibm.com/docs/en/jfsm/1.1.2.1?topic=queries-xsd-data-types + */ +public enum LiteralType implements IDataType +{ + + BOOLEAN("xsd:boolean"), + INTEGER("xsd:integer"), + DOUBLE("xsd:double"), + + DECIMAL("xsd:decimal"), + FLOAT("xsd:float"), + DATETIME("xsd:dateTime"), + STRING("xsd:string"), + XML_LITERAL("rdf:XMLLiteral"); + + final String typeName; + + LiteralType(String typeName) + { + this.typeName = typeName; + } + + @Override + public String getTypeName() + { + return typeName; + } +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/MetadataEntry.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/MetadataEntry.java new file mode 100644 index 0000000..bba81cf --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/MetadataEntry.java @@ -0,0 +1,103 @@ +package ch.eth.sis.rocrate.facade; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class MetadataEntry implements IMetadataEntry +{ + String id; + + String type; + + Map props; + + Map> references; + + List childrenIdentifiers = new ArrayList<>(); + + List parentIdentifiers = new ArrayList<>(); + + public MetadataEntry() + { + } + + public MetadataEntry(String id, String type, Map props, + Map> references) + { + this.id = id; + this.type = type; + this.props = props; + this.references = references; + } + + public String getId() + { + return id; + } + + @Override + public String getClassId() + { + return type; + } + + @Override + public Map getValues() + { + return props; + } + + @Override + public Map> getReferences() + { + return references; + } + + public void setId(String id) + { + this.id = id; + } + + @Override + public String getType() + { + return type; + } + + public void setType(String type) + { + this.type = type; + } + + public void addChildIdentifier(String a) + { + childrenIdentifiers.add(a); + } + + public void addParentIdentifier(String a) + { + parentIdentifiers.add(a); + } + + public List getChildrenIdentifiers() + { + return childrenIdentifiers; + } + + public List getParentIdentifiers() + { + return parentIdentifiers; + } + + public void setProps(Map props) + { + this.props = props; + } + + public void setReferences(Map> references) + { + this.references = references; + } +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/RdfsClass.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/RdfsClass.java new file mode 100644 index 0000000..cc50d79 --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/RdfsClass.java @@ -0,0 +1,86 @@ +package ch.eth.sis.rocrate.facade; + +import java.util.ArrayList; +import java.util.List; + +public class RdfsClass implements IType +{ + String id; + + String type; + + List subClassOf; + + List ontologicalAnnotations; + + List rdfsProperties; + + public RdfsClass() + { + this.subClassOf = new ArrayList<>(); + this.ontologicalAnnotations = new ArrayList<>(); + this.rdfsProperties = new ArrayList<>(); + + } + + public String getId() + { + return id; + } + + @Override + public List getSubClassOf() + { + return subClassOf; + } + + @Override + public List getOntologicalAnnotations() + { + return ontologicalAnnotations; + } + + /** + * This is a convenience method for adding a property to a class. + * + */ + public void addProperty(TypeProperty rdfsProperty) + { + List domainIncludes = rdfsProperty.getDomainIncludes(); + if (domainIncludes == null) + { + domainIncludes = new ArrayList<>(); + rdfsProperty.setDomainIncludes(domainIncludes); + } + if (id == null) + { + throw new IllegalArgumentException("Class id is null"); + } + domainIncludes.add(id); + } + + public void setId(String id) + { + this.id = id; + } + + public String getType() + { + return "rdfs:Class"; + } + + public void setType(String type) + { + this.type = type; + } + + public void setSubClassOf(List subClassOf) + { + this.subClassOf = subClassOf; + } + + public void setOntologicalAnnotations(List ontologicalAnnotations) + { + this.ontologicalAnnotations = ontologicalAnnotations; + } +} diff --git a/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/TypeProperty.java b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/TypeProperty.java new file mode 100644 index 0000000..98ddb0f --- /dev/null +++ b/0.1.x/lib/java/src/java/ch/eth/sis/rocrate/facade/TypeProperty.java @@ -0,0 +1,81 @@ +package ch.eth.sis.rocrate.facade; + +import java.util.ArrayList; +import java.util.List; + +public class TypeProperty implements IPropertyType +{ + List domainIncludes; + + List rangeIncludes; + + String id; + + List ontologicalAnnotations = new ArrayList<>(); + + public List getDomainIncludes() + { + return domainIncludes; + } + + public void setDomainIncludes(List domainIncludes) + { + this.domainIncludes = domainIncludes; + } + + public void setRangeIncludes(List rangeIncludes) + { + this.rangeIncludes = rangeIncludes; + } + + public String getId() + { + return id; + } + + @Override + public List getDomain() + { + return getDomainIncludes(); + } + + @Override + public List getRange() + { + return rangeIncludes; + } + + @Override + public List getOntologicalAnnotations() + { + return ontologicalAnnotations; + } + + public void setId(String id) + { + this.id = id; + } + + public void setOntologicalAnnotations(List ontologicalAnnotations) + { + this.ontologicalAnnotations = ontologicalAnnotations; + } + + public void setTypes(List types) + { + this.rangeIncludes = new ArrayList<>(types.stream().map(IDataType::getTypeName).toList()); + } + + public void addType(IDataType type) + { + if (this.rangeIncludes == null) + { + this.rangeIncludes = new ArrayList<>(); + } + if (!this.rangeIncludes.contains(type.getTypeName())) + { + this.rangeIncludes.add(type.getTypeName()); + } + + } +} diff --git a/0.1.x/spec.md b/0.1.x/spec.md new file mode 100644 index 0000000..6dcf541 --- /dev/null +++ b/0.1.x/spec.md @@ -0,0 +1,300 @@ +# Profile/Module - RO-Crate Convention to Include Schema and Metadata + +**Index:** + +- [Version](#version) +- [Definitions](#definitions) +- [Goals](#goals) +- [Technologies and Usage](#technologies-and-usage) + - [Schema Representation](#schema-representation) + - [RDFS Class](#rdfs-class) + - [RDFS Property](#rdfs-class) + - [Metadata Representation](#metadata-representation) + - [RDF Metadata Entry](#rdf-metadata-entry) +- [Reference Examples](#reference-examples-for-both-schema-and-entries) +- [API](#api) + - [Schema Representation DTOs](#schema-representation-dtos) + - [Metadata Representation DTOs](#metadata-representation-dtos) + - [Additional RO-Crate API Methods](#additional-ro-crate-api-methods) +- [API Reference Implementation in Java](#api-reference-implementation-in-java) +- [API Reference Examples in Java](#api-reference-examples-in-java) +- [Ongoing Work](#ongoing-work) +- [Possible Future Directions](#possible-future-directions) +- [People](#people) + +# Version + +0.1.0, initial version, compatible with RO-Crate 1.1 + + +# Definitions + +We use the following definitions in our proposal. + +- Schema: A logical design that defines the structure, organization and relationship between data. +- Metadata: data of a database adhering to the schema. +- Ontology: A set of concepts and the relationships between these concepts. + +# Goals + +This proposal SHOULD allow the means to exchange a database schema and database contents in a +standardized way. + +As consequence, Integrations SHOULD NOT need to parse individual files in non-standardized formats +anymore to obtain such information but MAY use the Ro-Crate API for such purpose. + +Since the goal is that multiple established systems can adhere to it, this poses the +additional problem that are multiple schemas in use for similar concepts. +To address this, we propose a way to annotate our schemas with ontological information. +The ontologies allow identification of shared concepts. +Knowing which concepts are shared allows easier integration for different schemas. + +Establishing such a format for interoperability would also benefit independent interoperability +efforts, as they would be available for reuse in other interoperability projects. + +This specification is made to be usable in Ro-Crate 1.1, as such: +- It SHOULD NOT add new keywords. +- It SHOULD establish a convention that can be used by the RO-Crate API to read/write the information. + +# Technologies and Usage + +- [RDF](https://www.w3.org/RDF/): Resource Description Framework is a specification developed by the + World Wide Web + Consortium (W3C) to provide a framework for representing and exchanging data on the web in a + structured way. RDF allows information to be described in terms of subject-predicate-object + triples, which form a graph of interconnected data. RDF can be serialized in different formats, + including JSON-LD as used by RO-Crate. +- [RDFS](https://www.w3.org/TR/rdf-schema/): Resource Description Framework Schema is a + specification developed by the World Wide Web Consortium (W3C) that extends RDF (Resource + Description Framework). RDFS provides a way to define the structure and relationships of RDF data, + allowing for the creation of vocabularies and the specification of classes, properties, and + hierarchies in an RDF dataset. +- [OWL](https://www.w3.org/OWL/): Web Ontology Language is a formal language used to define and + represent ontologies on the web. +- [XSD](https://www.w3.org/TR/xmlschema11-1/): XML Schema Definition is a language used to define + the structure, content, and constraints of XML documents. It will be used in this specification to + express primitive type. + +## Schema Representation +Because the schema is graph-based this can be easily integrated into the RO-Crate graph. + +The schema could also be included in a separate file in a future version of this specification. + +Ontologies are added using OWL's `equivalentClass` and `equivalentProperty` properties. + +What are the advantages of this? + +- the format is backward compatible +- this only uses features that RO-Crate already provides, no additional keywords are required +- Common format for export that prevents `n * (n - 1)` integration situation +- Thorough description of metadata, better automated checking and read-in + +**Formal description:** + +RO-Crate MUST include a graph description of the schema. +This is expressed using 2 types: + +- RDFS Class +- RDFS Property + +### RDFS Class + +Based on RDFS classes, these can be used as object and subjects of triples. + +| Type/Property | Required? | Description | +|---------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| @id | MUST | ID of the entry | +| @type | MUST | Is `rdfs:Class` | +| owl:equivalentClass | MAY | Ontological annotation https://www.w3.org/TR/owl-ref/#equivalentClass-def | +| rdfs:subClassOf | MUST | Used to indicate inheritance. Each entry has to inherit from something, this can be a base type. https://www.w3.org/TR/rdf-schema/#ch_subclassof | + +### RDFS Property + +RDFS Properties, these represent predicates in triples. +They also specify, which classes they can interact with. + +| Type/Property | Required? | Description | +|------------------------|-----------|----------------------------------------------------------------------------| +| @id | MUST | ID of the entry | +| @type | MUST | Is `rdfs:Property` | +| owl:equivalentProperty | MAY | Ontological annotation https://www.w3.org/TR/owl-ref/#equivalentClass-def | +| schema:domainIncludes | MUST | Describes the possible types of the subject. This can be one or many. | +| schema:rangeIncludes | MUST | Describes the possible types of the object. This can be one or many. | + +## Metadata Representation + +**Formal description:** + +RO-Crate MUST include a graph description of the metadata entries. +This is expressed using 1 type: + +- Metadata Entry + +### RDF Metadata Entry + +A metadata entry, described by a RDFS class. + +| Type/Property | Required? | Description | +|---------------|-----------|-----------------------------------------| +| @id | MUST | ID of the entry | +| @type | MUST | Type of the entry, MUST be a RDFS Class | + +Further properties are included as specified in the RDFS description as fields. + +# Reference Examples for both Schema and Entries + +We created a small example. It can be found under: +`./examples/ro-crate-1.1/ro-crate-metadata/ro-crate-metadata.json.` +This describes the export +of `./examples/reference-openbis-export`. + +# API + +**Formal description:** + +To be general, the API uses a lot of strings. This allows flexibility in the classes being used. + +The interfaces are shown using Java since is a statically typed language, but they can be +implemented in most languages, +including Python and Javascript. + +## Schema Representation DTOs + +```Java + +/* Represents a class, if we are talking about a schema, it is closely related with the definition of a table or type */ +interface IType +{ + + /* Returns the ID of this type */ + String getId(); + + /* Returns IDs of the types this type inherits from */ + List getSubClassOf(); + + /* Returns the ontological annotations of this type */ + List getOntologicalAnnotations(); + +} + +/* Represents a property in a graph, if we are talking about a schema, is closely related with a table column or type property */ +interface IPropertyType +{ + + /* Returns the ID of this property type */ + String getId(); + + /* Return possible values for the subject of this property type */ + List getDomain(); + + /* Return possible values for the object of this property type */ + List getRange(); + + /* Returns the ontological annotations of this property type */ + List getOntologicalAnnotations(); + + } +``` + +## Metadata Representation DTOs + +```Java +/* Represents a metadata entity. It is described */ +interface IMetadataEntry +{ + + + /** + * Returns the ID of this entry + */ + String getId(); + + /* Returns the type ID of this entry */ + String getClassId(); + + /* These are key-value pairs for serialization. These are single-valued. + * Serializable classes are: String, Number and Boolean */ + Map getValues(); + + /* These are references to other objects in the graph. + * Each key may have one or more references */ + Map> getReferences(); +} +``` + +## Additional RO-Crate API Methods + + +```Java +/* The API to program against, this wraps around existing RO-Crate APIs. */ +interface ISchemaFacade +{ + + /* Adds a single class */ + void addType(IType rdfsClass); + + /** Retrieves all Classes */ + List getTypes(); + + /* Get a single type by its ID */ + IType getTypes(String id); + + /* Adds a single property */ + void addPropertyType(IPropertyType property); + + /* Get all Properties */ + List getPropertyTypes(); + + /* Gets a single property by its ID. */ + IPropertyType getPropertyType(String id); + + /* Add a single metadata entry */ + void addEntry(IMetadataEntry entry); + + /* Get a single metadata entry by its ID */ + IMetadataEntry getEntry(String id); + + /* Get all metadata entities */ + List getEntries(String rdfsClassId); + +} +``` + +# API Reference Implementation in Java + +A working implementation of the API for Java (source and compiled) can be found +under: `./lib/src`. + +A compiled jar can be found under: `./lib/java/bin`. +The dependencies are specified in the module's `build.gradle` +file: `./lib/java/src/build.gradle`. + +# API Reference Examples in Java + +Working examples of the API in java to read and write can be found +at: `./`, specifically the class +files + +- `./lib/java/src/java/ch/eth/sis/rocrate/example/ReadExample.java` +- `./lib/java/src/java/ch/eth/sis/rocrate/example/WriteExample.java` + +# Ongoing Work + +- Adding complex data types +- Using `rdfs:Label` to indicate the original name of a property (this could also help in resolving + properties with the same name) +- Validation of data types expressed in the schema, e.g. enforcing ISO 8601 for dates +- Bundling ontologies in the RO-Crate +- Find a way of specifying other data formats + +# Possible Future Directions + +- We would like to store the schema and metadata information in separate files and indicate the + format of the file in `ro-crate-metadata.json` +- Other serialization formats could be supported when using separate files +- Adding methods for deleting to facade to have all CRUD operations + +# People + +- Andreas Meier (andreas.meier@ethz.ch) +- Juan Fuentes (juan.fuentes@id.ethz.ch) diff --git a/0.2.x/examples/reference-openbis-export/metadata.xlsx b/0.2.x/examples/reference-openbis-export/metadata.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f4fbf4da498a3cb1a6e790d48ca2c820972a1b47 GIT binary patch literal 11065 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 literal 0 HcmV?d00001 diff --git a/0.2.x/examples/ro-crate-1.1/dist/rocrate.zip b/0.2.x/examples/ro-crate-1.1/dist/rocrate.zip new file mode 100644 index 0000000000000000000000000000000000000000..498d3c9a1a7ac566dce15b522f2250838b05d6b3 GIT binary patch literal 9992 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 literal 0 HcmV?d00001 diff --git a/0.2.x/lib/java/src/build.gradle b/0.2.x/lib/java/src/build.gradle new file mode 100644 index 0000000..7d6816f --- /dev/null +++ b/0.2.x/lib/java/src/build.gradle @@ -0,0 +1,74 @@ +plugins { + id 'java' + id 'application' +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType(JavaCompile) { + options.compilerArgs << '-parameters' +} + +repositories { + ivy { + ivyPattern "https://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/[organisation]/[module]/[revision]/ivy.xml" + artifactPattern "https://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]" + } + mavenCentral() +} + +//'openbis:openbis-v3-api-batteries-included:6.5.0', +//files('libs/openBIS-API-V3-batteries-included-SNAPSHOT-r1718006152.jar'), +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core' + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'org.slf4j:slf4j-api:2.0.16' + + implementation 'edu.kit.datamanager:ro-crate-java:2.0.1' + testImplementation 'junit:junit:4.13.1' + testImplementation('jmock:jmock:2.5.1') + +} + +configurations.configureEach { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + + // https://docs.gradle.org/current/userguide/resolution_rules.html + if (details.requested.group == 'com.fasterxml.jackson.core' && details.requested.name == 'jackson-databind') { + details.useVersion '2.18.0' + } + if (details.requested.group == 'com.fasterxml.jackson.core' && details.requested.name == 'jackson-core') { + details.useVersion '2.18.0' + } + if (details.requested.group == 'org.slf4j') { + details.useVersion '2.0.16' + } + } +} + + +tasks.register('buildRdfTool', Jar) { + dependsOn tasks.build + duplicatesStrategy 'include' + + archiveBaseName.set('lib-rdf-tool') + includeEmptyDirs = false + + manifest { + attributes( + 'Main-Class': 'ch.ethz.sis.rdf.main.RDFCommandLine' + ) + } + + from(zipTree(tasks.jar.outputs.files.singleFile)) { + include '**/*' + } + + from(configurations.runtimeClasspath.collect { zipTree(it) }) { + include '**/*' + exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA' + } +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/SchemaFacade.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/SchemaFacade.java new file mode 100644 index 0000000..6a1637f --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/SchemaFacade.java @@ -0,0 +1,566 @@ +package ch.eth.sis.rocrate; + +import ch.eth.sis.rocrate.facade.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.entities.data.DataEntity; + +import java.io.Serializable; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class SchemaFacade implements ISchemaFacade +{ + + private final static String RDFS_CLASS = "rdfs:Class"; + + private final static String RDFS_PROPERTY = "rdfs:Property"; + + public static final String EQUIVALENT_CLASS = "owl:equivalentClass"; + + public static final String EQUIVALENT_CONCEPT = "owl:equivalentProperty"; + + public static final String TYPE_RESTRICTION = "owl:restriction"; + + + String rangeIdentifier = "schema:rangeIncludes"; + + String domainIdentifier = "schema:domainIncludes"; + + public static final String OWL_MIN_CARDINALITY = "owl:minCardinality"; + + public static final String OWL_MAX_CARDINALITY = "owl:maxCardinality"; + + public static final String OWL_RESTRICTION = "owl:restriction"; + + public static final String ON_PROPERTY = "owl:onProperty"; + + + public static final String RDFS_LABEL = "rdfs:label"; + + public static final String RDFS_COMMENT = "rdfs:comment"; + + + + Pattern p; + + String localPrefix = ":"; + + private Map types; + + private Map propertyTypes; + + private Map metadataEntries; + + private final RoCrate crate; + + @Override + public RoCrate getCrate() + { + return crate; + } + + public SchemaFacade(String name, String description, String dateString, + String licenseIdentifier, Map context) + { + RoCrate.RoCrateBuilder roCrateBuilder = + new RoCrate.RoCrateBuilder(name, description, dateString, + licenseIdentifier); + roCrateBuilder.addValuePairToContext("schema", + "https://www.w3.org/TR/rdf-schema"); + roCrateBuilder.addValuePairToContext("owl", + "https://www.w3.org/TR/owl-ref"); + for (Map.Entry keyVal : context.entrySet() + ) + { + roCrateBuilder.addValuePairToContext(keyVal.getKey(), keyVal.getValue()); + } + + this.crate = roCrateBuilder.build(); + this.types = new LinkedHashMap<>(); + this.propertyTypes = new LinkedHashMap<>(); + this.metadataEntries = new LinkedHashMap<>(); + } + + public SchemaFacade(RoCrate crate) + { + this.crate = crate; + this.types = new LinkedHashMap<>(); + this.propertyTypes = new LinkedHashMap<>(); + this.metadataEntries = new LinkedHashMap<>(); + } + + public static SchemaFacade of(RoCrate crate) throws JsonProcessingException + { + SchemaFacade schemaFacade = new SchemaFacade(crate); + schemaFacade.parseEntities(); + return schemaFacade; + + } + + @Override + public void addType(IType rdfsClass) + { + + DataEntity.DataEntityBuilder builder = new DataEntity.DataEntityBuilder(); + builder.addProperty("@id", rdfsClass.getId()); + builder.addProperty("@type", RDFS_CLASS); + builder.addProperty(RDFS_LABEL, rdfsClass.getLabel()); + builder.addProperty(RDFS_COMMENT, rdfsClass.getComment()); + + for (IRestriction restriction : rdfsClass.getResstrictions()) + { + DataEntity.DataEntityBuilder restrictionBuilder = new DataEntity.DataEntityBuilder(); + restrictionBuilder.addProperty("@id", restriction.getId()); + restrictionBuilder.addProperty("@type", TYPE_RESTRICTION); + restrictionBuilder.addIdProperty(ON_PROPERTY, restriction.getPropertyType().getId()); + restrictionBuilder.addProperty(OWL_MIN_CARDINALITY, restriction.getMinCardinality()); + restrictionBuilder.addProperty(OWL_MAX_CARDINALITY, restriction.getMaxCardinality()); + builder.addIdProperty(OWL_RESTRICTION, restriction.getId()); + crate.addDataEntity(restrictionBuilder.build()); + } + + + rdfsClass.getSubClassOf().forEach(x -> builder.addIdProperty("rdfs:subClassOf", x)); + this.types.put(rdfsClass.getId(), rdfsClass); + DataEntity entity = builder.build(); + entity.addIdListProperties(EQUIVALENT_CLASS, rdfsClass.getOntologicalAnnotations()); + crate.addDataEntity(entity); + + } + + @Override + public List getTypes() + { + return this.types.values().stream().toList(); + } + + @Override + public IType getTypes(String id) + { + return this.types.get(id); + } + + @Override + public void addPropertyType(IPropertyType rdfsProperty) + { + DataEntity.DataEntityBuilder builder = new DataEntity.DataEntityBuilder(); + + builder.setId(rdfsProperty.getId()); + builder.addProperty("@type", RDFS_PROPERTY); + builder.addProperty(RDFS_LABEL, rdfsProperty.getLabel()); + builder.addProperty(RDFS_COMMENT, rdfsProperty.getComment()); + + var stuff = builder.build(); + stuff.addIdListProperties("schema:rangeIncludes", + rdfsProperty.getRange()); + stuff.addIdListProperties("schema:domainIncludes", + rdfsProperty.getDomain().stream().map(x -> x.getId()).collect(Collectors.toList())); + stuff.addIdListProperties(EQUIVALENT_CONCEPT, + rdfsProperty.getOntologicalAnnotations()); + crate.addDataEntity(stuff); + propertyTypes.put(rdfsProperty.getId(), rdfsProperty); + + } + + @Override + public void addRestriction(IRestriction restriction) + { + + } + + @Override + public List getPropertyTypes() + { + return propertyTypes.values().stream().toList(); + } + + @Override + public IPropertyType getPropertyType(String id) + { + return propertyTypes.get(id); + } + + @Override + public void addEntry(IMetadataEntry metaDataEntry) + { + DataEntity.DataEntityBuilder builder = new DataEntity.DataEntityBuilder(); + builder.setId(metaDataEntry.getId()); + for (String type : metaDataEntry.getTypes()) + { + builder.addType(type); + } + ObjectMapper objectMapper = new ObjectMapper(); + + metaDataEntry.getValues().forEach((s, o) -> { + if (o instanceof Double) + { + builder.addProperty(s, (Double) o); + } else if (o instanceof Integer) + { + builder.addProperty(s, (Integer) o); + } else if (o instanceof Boolean) + { + builder.addProperty(s, (Boolean) o); + } else if (o instanceof String) + { + builder.addProperty(s, o.toString()); + } else if (o == null) + { + builder.addProperty(s, objectMapper.nullNode()); + } + }); + DataEntity dataEntity = builder.build(); + metaDataEntry.getReferences().forEach(dataEntity::addIdListProperties); + + crate.addDataEntity(dataEntity); + + } + + @Override + public IMetadataEntry getEntry(String id) + { + return metadataEntries.get(id); + } + + @Override + public List getEntries(String rdfsClassId) + { + return metadataEntries.values().stream() + .filter(x -> matchClasses(resolvePrefixSingleValue(rdfsClassId), x)) + .toList(); + } + + @Override + public List getRestrictions() + { + return null; + } + + private boolean matchClasses(String queryClassId, IMetadataEntry entry) + { + if (entry.getTypes().stream().anyMatch(x -> x.equals(queryClassId))) + { + return true; + } + + return entry.getTypes().stream() + .map(x -> p.matcher(x)) + .map(x -> x.replaceAll("_:")) + .anyMatch(x -> x.equals(queryClassId)); + + } + + private void parseEntities() throws JsonProcessingException + { + + localPrefix = getLocalPrefix(crate.getJsonMetadata()); + Map keyValuePairs = getKeyValPairsFromMetadata(crate.getJsonMetadata()); + for (var keyValPair : keyValuePairs.entrySet()) + { + if (keyValPair.getValue().equals("http://schema.org/rangeIncludes")) + { + rangeIdentifier = keyValPair.getKey(); + } + if (keyValPair.getValue().equals("http://schema.org/domainIncludes")) + { + domainIdentifier = keyValPair.getKey(); + } + } + + + + + Map properties = new LinkedHashMap<>(); + Map classes = new LinkedHashMap<>(); + Map entries = new LinkedHashMap<>(); + + Map restrictionToTypeId = new LinkedHashMap<>(); + + + + for (DataEntity entity : crate.getAllDataEntities()) + { + String type = entity + .getProperty("@type").asText(); + String id = + entity.getProperty("@id") + .asText(); + + switch (type) + { + case "rdfs:Class" -> + { + Type myType = new Type(); + myType.setSubClassOf(parseMultiValued(entity, "rdfs:subClassOf")); + myType.setOntologicalAnnotations( + parseMultiValued(entity, EQUIVALENT_CLASS)); + myType.setId(resolvePrefixSingleValue(id)); + classes.put(resolvePrefixSingleValue(id), myType); + parseMultiValued(entity, OWL_RESTRICTION).forEach( + x -> restrictionToTypeId.put(x, myType)); + + } + + + } + + } + + for (DataEntity entity : crate.getAllDataEntities()) + { + String type = entity + .getProperty("@type").asText(); + String id = + entity.getProperty("@id") + .asText(); + + switch (type) + { + case "rdfs:Property" -> + { + PropertyType rdfsProperty = new PropertyType(); + rdfsProperty.setId(resolvePrefixSingleValue(id)); + + rdfsProperty.setOntologicalAnnotations( + parseMultiValued(entity, EQUIVALENT_CONCEPT)); + + List rawRange = parseMultiValued(entity, rangeIdentifier); + + List dataTypes = rawRange.stream() + .filter(LiteralType::isLiteralType) + .map(LiteralType::getByTypeName) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + List types = rawRange.stream() + .filter(x -> !LiteralType.isLiteralType(x)) + .map(this::resolvePrefixSingleValue) + .map(classes::get) + .collect(Collectors.toList()); + + dataTypes.stream().forEach(rdfsProperty::addDataType); + types.forEach(rdfsProperty::addType); + + rdfsProperty.setDomainIncludes( + parseMultiValued(entity, domainIdentifier).stream() + .map(x -> resolvePrefixSingleValue(x)) + .map(classes::get).collect( + Collectors.toList())); + properties.put(resolvePrefixSingleValue(id), rdfsProperty); + + } + } + + } + + for (DataEntity entity : crate.getAllDataEntities()) + { + String type = entity.getProperty("@type").asText(); + String id = + entity.getProperty("@id") + .asText(); + + if (type.equals(OWL_RESTRICTION)) + { + String onProperty = parseMultiValued(entity, ON_PROPERTY).get(0); + int minCardinality = + entity.getProperty(OWL_MIN_CARDINALITY).numberValue().intValue(); + + int maxCardinality = + entity.getProperty(OWL_MAX_CARDINALITY).numberValue().intValue(); + Restriction restriction = + new Restriction(id, properties.get(onProperty), minCardinality, + maxCardinality); + restrictionToTypeId.get(id).addRestriction(restriction); + } + + } + + + for (var entity : crate.getAllDataEntities()) + { + Set type = parseTypes(entity); + + String id = + entity.getProperty("@id") + .asText(); + if (!doesTypeExist(type, classes, localPrefix)) + { + continue; + } + + Map entryProperties = new LinkedHashMap<>(); + MetadataEntry entry = new MetadataEntry(); + entry.setId(id); + + entry.setTypes(resolvePrefix(type)); + Map> references = new LinkedHashMap<>(); + ObjectMapper objectMapper = new ObjectMapper(); + Map keyVals = + objectMapper.readValue(entity.getProperties().toString(), HashMap.class); + for (Map.Entry a : keyVals.entrySet()) + { + if (properties.containsKey(a.getKey())) + { + IPropertyType property = properties.get(a.getKey()); + if (property.getRange().stream().anyMatch(x -> x.startsWith("xsd:"))) + { + entryProperties.put(a.getKey(), a.getValue().toString()); + } else + { + List refs = parseMultiValued(entity, a.getKey()); + references.put(a.getKey(), refs); + } + } + } + entry.setProps(entryProperties); + entry.setReferences(references); + entries.put(id, entry); + } + System.out.println("Done"); + this.types = classes; + this.propertyTypes = properties; + this.metadataEntries = entries; + + } + + private Set resolvePrefix(Set types) + { + Pattern placeholderPattern = Pattern.compile("^_:"); + + LinkedHashSet newTypes = new LinkedHashSet(); + for (String type : types) + { + newTypes.add(placeholderPattern.matcher(type).replaceAll(localPrefix)); + + } + return newTypes; + } + + private String resolvePrefixSingleValue(String type) + { + Pattern placeholderPattern = Pattern.compile("^_:"); + + return placeholderPattern.matcher(type).replaceAll(localPrefix); + } + + private List parseMultiValued(DataEntity dataEntity, String key) + { + JsonNode node = dataEntity.getProperty(key); + if (node instanceof ObjectNode) + { + return List.of(node.get("@id").textValue()); + } + if (node instanceof ArrayNode arrayNode) + { + List accumulator = new ArrayList<>(); + arrayNode.elements().forEachRemaining( + x -> accumulator.add(x.get("@id").textValue()) + ); + return accumulator; + } + return List.of(); + + } + + private Set parseTypes(DataEntity entity) + { + JsonNode typeResult = entity.getProperty("@type"); + if (typeResult.isTextual()) + { + return Set.of(typeResult.textValue()); + } + if (typeResult.isArray()) + { + ArrayNode arrayNode = (ArrayNode) typeResult; + Set typeroos = new LinkedHashSet<>(); + arrayNode.forEach(x -> typeroos.add(x.textValue())); + return typeroos; + + } + throw new RuntimeException("Unknown node type for @type"); + + } + + Map getKeyValPairsFromMetadata(String metaDataJson) + throws JsonProcessingException + { + ObjectMapper objectMapper = new ObjectMapper(); + LinkedHashMap vals = objectMapper.readValue(metaDataJson, LinkedHashMap.class); + + if (vals.get("@context") instanceof LinkedHashMap) + { + + return (Map) vals.get("@context"); + + } + + if (vals.get("@context") instanceof String) + { + return Map.of(); + } + + List nodes = (List) vals.get("@context"); + Map key_vals = (Map) nodes.get(1); + + Map result = new LinkedHashMap<>(); + for (Object a : key_vals.entrySet()) + { + Map.Entry b = (Map.Entry) a; + result.put(b.getKey().toString(), b.getValue().toString()); + } + + return result; + } + + String getLocalPrefix(String jsonMetaData) throws JsonProcessingException + { + Map keyVals = getKeyValPairsFromMetadata(jsonMetaData); + for (Map.Entry entry : keyVals.entrySet()) + { + if (entry.getValue().equals("_:")) + { + return entry.getKey() + ":"; + } + + } + return ""; + } + + boolean doesTypeExist(Set types, Map classes, String localPrefix) + { + p = Pattern.compile("^" + localPrefix + ":", Pattern.CASE_INSENSITIVE); + + boolean somethingFound = false; + for (String type : types) + { + boolean typeFound = false; + + Matcher m = p.matcher(type); + if (classes.containsKey(type)) + { + typeFound = true; + } + if (classes.containsKey(m.replaceAll("_:"))) + { + typeFound = true; + } + + if (!typeFound) + { + System.out.println("Type " + type + " does not seem to be part of the schema"); + } + somethingFound = somethingFound || typeFound; + } + + return somethingFound; + + } + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/ReadExample.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/ReadExample.java new file mode 100644 index 0000000..f3e3f8f --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/ReadExample.java @@ -0,0 +1,38 @@ +package ch.eth.sis.rocrate.example; + +import ch.eth.sis.rocrate.SchemaFacade; +import com.fasterxml.jackson.core.JsonProcessingException; +import edu.kit.datamanager.ro_crate.RoCrate; +import edu.kit.datamanager.ro_crate.reader.FolderReader; +import edu.kit.datamanager.ro_crate.reader.RoCrateReader; + +public class ReadExample +{ + /** + * This reads an RO-Crate with schema information and prints out + * If you need a compatible RO-Crate, run WriteExample.java first. + * Takes one optional command line argument for an output path, if not provided, it will read from + * ./out + * @param args + * @throws JsonProcessingException + */ + public static void main(String[] args) throws JsonProcessingException + { + String path = args.length >= 1 ? args[0] : "out"; + RoCrateReader roCrateFolderReader = new RoCrateReader(new FolderReader()); + RoCrate crate = roCrateFolderReader.readCrate(path); + SchemaFacade schemaFacade = SchemaFacade.of(crate); + schemaFacade.getTypes().forEach( + x -> System.out.println("RDFS Class " + x.getId()) + ); + schemaFacade.getPropertyTypes().forEach( + x -> System.out.println("RDFS Property " + x.getId()) + ); + schemaFacade.getEntries(schemaFacade.getTypes().get(0).getId()).forEach( + x -> System.out.println("Metadata entry " + x.getId()) + ); + + } + + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/WriteExample.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/WriteExample.java new file mode 100644 index 0000000..9f5fae5 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/example/WriteExample.java @@ -0,0 +1,108 @@ +package ch.eth.sis.rocrate.example; + +import ch.eth.sis.rocrate.SchemaFacade; +import ch.eth.sis.rocrate.facade.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import edu.kit.datamanager.ro_crate.writer.FolderWriter; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class WriteExample +{ + + /** + * This is a small example that adds one type for a textual resource with two properties. One + * of the properties is a literal, date, while the other references the creator. The creator + * type has its own properties. Some of the properties have ontological annotations to clarify + * their meanings. + * Takes one optional command line argument for an output path, if not provided, it will write to + * ./out + * + * @param args + * @throws JsonProcessingException + */ + public static void main(String[] args) throws JsonProcessingException + { + + ISchemaFacade schemaFacade = + new SchemaFacade("name", "description", "2024-12-04T07:53:11Z", "licenceIdentifier", + Map.of()); + + { + Type type = new Type(); + type.setId("TextResource"); + type.setSubClassOf(List.of("https://schema.org/Thing")); + type.setOntologicalAnnotations( + List.of("https://www.dublincore.org/specifications/dublin-core/dcmi-terms/dcmitype/Text/")); + schemaFacade.addType(type); + + Type creatorType = new Type(); + creatorType.setId("Creator"); + creatorType.setSubClassOf(List.of("https://schema.org/Thing")); + creatorType.setOntologicalAnnotations( + List.of("https://www.dublincore.org/specifications/dublin-core/dcmi-terms/terms/creator//")); + schemaFacade.addType(type); + schemaFacade.addType(creatorType); + { + + } + + + + PropertyType property = new PropertyType(); + property.setId("hasDateSubmitted"); + property.addDataType(LiteralType.DATETIME); + property.setOntologicalAnnotations( + List.of("https://www.dublincore.org/specifications/dublin-core/dcmi-terms/terms/dateSubmitted/")); + type.addProperty(property); + + PropertyType propertyCreator = new PropertyType(); + propertyCreator.setId("hasCreator"); + propertyCreator.addType(creatorType); + type.addProperty(propertyCreator); + + PropertyType name = new PropertyType(); + name.setId("hasName"); + name.addDataType(LiteralType.STRING); + creatorType.addProperty(name); + + PropertyType identifier = new PropertyType(); + identifier.setId("hasIdentifier"); + identifier.addDataType(LiteralType.STRING); + identifier.setOntologicalAnnotations( + List.of("https://www.dublincore.org/specifications/dublin-core/dcmi-terms/elements11/identifier/")); + creatorType.addProperty(identifier); + + + schemaFacade.addPropertyType(property); + schemaFacade.addPropertyType(propertyCreator); + schemaFacade.addPropertyType(identifier); + schemaFacade.addPropertyType(name); + + String creatorId = "creator1"; + IMetadataEntry creatorEntry = new MetadataEntry(creatorId, Set.of(creatorType.getId()), + Map.of("hasName", "John Author", "hasIdentifier", + "https://orcid.org/0000-0000-0000-0000"), Map.of()); + + IMetadataEntry metadataEntry = + new MetadataEntry("TextResource1", Set.of("TextResource"), + Map.of("hasDate", "2025-01-21T07:12:20Z"), + Map.of("hasCreator", List.of(creatorId))); + schemaFacade.addEntry(metadataEntry); + schemaFacade.addEntry(creatorEntry); + + } + + String path = args.length >= 1 ? args[0] : "out"; + + + + + FolderWriter folderWriter = new FolderWriter(); + folderWriter.save(schemaFacade.getCrate(), path); + + } + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IDataType.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IDataType.java new file mode 100644 index 0000000..6c9eb38 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IDataType.java @@ -0,0 +1,21 @@ +package ch.eth.sis.rocrate.facade; + +public interface IDataType +{ + String getTypeName(); + + static IDataType getArray(LiteralType literalType) + { //TODO static IDataType getArray(LiteralType literalType) + throw new UnsupportedOperationException("Feature incomplete. Contact assistance."); + } + + static IDataType getEnumerationf(String... values) + { //TODO static IDataType getEnumerationf(String... values) + throw new UnsupportedOperationException("Feature incomplete. Contact assistance."); + } + + static IDataType getCustomType(LiteralType basicType, String pattern) + { //TODO static IDataType getCustomType(LiteralType basicType, String pattern) + throw new UnsupportedOperationException("Feature incomplete. Contact assistance."); + } +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IMetadataEntry.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IMetadataEntry.java new file mode 100644 index 0000000..3db6160 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IMetadataEntry.java @@ -0,0 +1,30 @@ +package ch.eth.sis.rocrate.facade; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface IMetadataEntry +{ + + /** + * Returns the ID of this entry + */ + String getId(); + + /* Returns the type ID of this entry */ + String getClassId(); + + /* Returns the types of the entry */ + Set getTypes(); + + /* These are key-value pairs for serialization. These are single-valued. + * Serializable classes are: String, Number and Boolean */ + Map getValues(); + + /* These are references to other objects in the graph. + * Each key may have one or more references */ + Map> getReferences(); + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IPropertyType.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IPropertyType.java new file mode 100644 index 0000000..01587d4 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IPropertyType.java @@ -0,0 +1,25 @@ +package ch.eth.sis.rocrate.facade; + +import java.util.List; + +public interface IPropertyType +{ + /* Returns the ID of this property type */ + String getId(); + + /* Return possible values for the subject of this property type */ + List getDomain(); + + /* Return possible values for the object of this property type */ + List getRange(); + + /* Returns the ontological annotations of this property type */ + List getOntologicalAnnotations(); + + /* Returns a human-readable description of this type */ + String getComment(); + + /* Returns a human-readable label of this type */ + String getLabel(); + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IRestriction.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IRestriction.java new file mode 100644 index 0000000..f37d3a7 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IRestriction.java @@ -0,0 +1,13 @@ +package ch.eth.sis.rocrate.facade; + +public interface IRestriction +{ + + String getId(); + + IPropertyType getPropertyType(); + + int getMinCardinality(); + + int getMaxCardinality(); +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/ISchemaFacade.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/ISchemaFacade.java new file mode 100644 index 0000000..f0b54d9 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/ISchemaFacade.java @@ -0,0 +1,45 @@ +package ch.eth.sis.rocrate.facade; + +import edu.kit.datamanager.ro_crate.RoCrate; + +import java.util.List; + +public interface ISchemaFacade +{ + + /* Get the crate being worked on */ + RoCrate getCrate(); + + /* Adds a single class */ + void addType(IType rdfsClass); + + /** Retrieves all Classes */ + List getTypes(); + + /* Get a single type by its ID */ + IType getTypes(String id); + + /* Adds a single property */ + void addPropertyType(IPropertyType property); + + /* Adds a restriction */ + void addRestriction(IRestriction restriction); + + /* Get all Properties */ + List getPropertyTypes(); + + /* Gets a single property by its ID. */ + IPropertyType getPropertyType(String id); + + /* Add a single metadata entry */ + void addEntry(IMetadataEntry entry); + + /* Get a single metadata entry by its ID */ + IMetadataEntry getEntry(String id); + + /* Get all metadata entities */ + List getEntries(String rdfsClassId); + + List getRestrictions(); + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IType.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IType.java new file mode 100644 index 0000000..03053b2 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/IType.java @@ -0,0 +1,27 @@ +package ch.eth.sis.rocrate.facade; + + +import java.util.List; + +public interface IType +{ + /* Returns the ID of this type */ + String getId(); + + /* Returns IDs of the types this type inherits from */ + List getSubClassOf(); + + /* Returns the ontological annotations of this type */ + List getOntologicalAnnotations(); + + /* Returns a human-readable description of this type */ + String getComment(); + + /* Returns a human-readable label of this type */ + String getLabel(); + + /* Get Restrictions placed on the properties of this type */ + List getResstrictions(); + + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/LiteralType.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/LiteralType.java new file mode 100644 index 0000000..fae7240 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/LiteralType.java @@ -0,0 +1,58 @@ +package ch.eth.sis.rocrate.facade; + +/** + * List of primitives as supported by xsd + * https://www.ibm.com/docs/en/jfsm/1.1.2.1?topic=queries-xsd-data-types + */ +public enum LiteralType implements IDataType +{ + + BOOLEAN("xsd:boolean"), + INTEGER("xsd:integer"), + DOUBLE("xsd:double"), + + DECIMAL("xsd:decimal"), + FLOAT("xsd:float"), + DATETIME("xsd:dateTime"), + STRING("xsd:string"), + XML_LITERAL("rdf:XMLLiteral"); + + final String typeName; + + LiteralType(String typeName) + { + this.typeName = typeName; + } + + @Override + public String getTypeName() + { + return typeName; + } + + public static boolean isLiteralType(String typeName) + { + for (LiteralType literalType : LiteralType.values()) + { + if (typeName.equals(literalType.getTypeName())) + { + return true; + } + } + return false; + } + + public static LiteralType getByTypeName(String typeName) + { + for (LiteralType literalType : LiteralType.values()) + { + if (typeName.equals(literalType.getTypeName())) + { + return literalType; + } + } + throw new IllegalArgumentException("Unknown literal type: " + typeName); + + } + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/MetadataEntry.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/MetadataEntry.java new file mode 100644 index 0000000..fc2972f --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/MetadataEntry.java @@ -0,0 +1,118 @@ +package ch.eth.sis.rocrate.facade; + +import java.io.Serializable; +import java.util.*; + +public class MetadataEntry implements IMetadataEntry +{ + String id; + + Set types; + + Map props; + + Map> references; + + List childrenIdentifiers = new ArrayList<>(); + + List parentIdentifiers = new ArrayList<>(); + + public MetadataEntry() + { + } + + public MetadataEntry(String id, Set types, Map props, + Map> references) + { + this.id = id; + this.types = types; + this.props = props; + this.references = references; + } + + public String getId() + { + return id; + } + + @Override + public String getClassId() + { + return null; + } + + @Override + public Map getValues() + { + return props; + } + + @Override + public Map> getReferences() + { + return references; + } + + public void setId(String id) + { + this.id = id; + } + + @Override + public Set getTypes() + { + return types; + } + + public void setTypes(Set types) + { + this.types = types; + } + + public void addChildIdentifier(String a) + { + childrenIdentifiers.add(a); + } + + public void addParentIdentifier(String a) + { + parentIdentifiers.add(a); + } + + public List getChildrenIdentifiers() + { + return childrenIdentifiers; + } + + public List getParentIdentifiers() + { + return parentIdentifiers; + } + + public void setProps(Map props) + { + this.props = props; + } + + public void setReferences(Map> references) + { + this.references = references; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + MetadataEntry entry = (MetadataEntry) o; + return Objects.equals(id, entry.id) && Objects.equals(types, entry.types); + } + + @Override + public int hashCode() + { + return Objects.hash(id, types); + } +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/PropertyType.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/PropertyType.java new file mode 100644 index 0000000..c606f4a --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/PropertyType.java @@ -0,0 +1,132 @@ +package ch.eth.sis.rocrate.facade; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PropertyType implements IPropertyType +{ + List domainIncludes; + + List rangeIncludes; + + List rangeeIndlucesDataType; + + String id; + + List ontologicalAnnotations = new ArrayList<>(); + + + String label; + + String comment; + + public PropertyType() + { + this.rangeIncludes = new ArrayList<>(); + this.rangeeIndlucesDataType = new ArrayList<>(); + this.ontologicalAnnotations = new ArrayList<>(); + + } + + public List getDomainIncludes() + { + return domainIncludes; + } + + public void setDomainIncludes(List domainIncludes) + { + this.domainIncludes = domainIncludes; + } + + + public String getId() + { + return id; + } + + @Override + public List getDomain() + { + return getDomainIncludes(); + } + + @Override + public List getRange() + { + Stream a = rangeIncludes.stream().map(x -> x.getId()); + Stream b = rangeeIndlucesDataType.stream().map(x -> x.getTypeName()); + return Stream.concat(a, b).collect(Collectors.toList()); + + + } + + @Override + public List getOntologicalAnnotations() + { + return ontologicalAnnotations; + } + + @Override + public String getComment() + { + return comment; + } + + @Override + public String getLabel() + { + return label; + } + + public void setId(String id) + { + this.id = id; + } + + public void setOntologicalAnnotations(List ontologicalAnnotations) + { + this.ontologicalAnnotations = ontologicalAnnotations; + } + + public void setTypes(List types) + { + this.rangeeIndlucesDataType = new ArrayList<>(types); + } + + public void addDataType(IDataType type) + { + if (this.rangeeIndlucesDataType == null) + { + this.rangeeIndlucesDataType = new ArrayList<>(); + } + if (!rangeeIndlucesDataType.contains(type)) + { + rangeeIndlucesDataType.add(type); + } + + } + + public void addType(IType type) + { + if (this.rangeIncludes == null) + { + this.rangeIncludes = new ArrayList<>(); + } + if (!this.rangeIncludes.contains(type)) + { + this.rangeIncludes.add(type); + } + } + + public void setLabel(String label) + { + this.label = label; + } + + public void setComment(String comment) + { + this.comment = comment; + } +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/RdfsClass.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/RdfsClass.java new file mode 100644 index 0000000..ce92fd7 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/RdfsClass.java @@ -0,0 +1,86 @@ +ackage ch.eth.sis.rocrate.facade; + +import java.util.ArrayList; +import java.util.List; + +public class RdfsClass implements IType +{ + String id; + + String type; + + List subClassOf; + + List ontologicalAnnotations; + + List rdfsProperties; + + public RdfsClass() + { + this.subClassOf = new ArrayList<>(); + this.ontologicalAnnotations = new ArrayList<>(); + this.rdfsProperties = new ArrayList<>(); + + } + + public String getId() + { + return id; + } + + @Override + public List getSubClassOf() + { + return subClassOf; + } + + @Override + public List getOntologicalAnnotations() + { + return ontologicalAnnotations; + } + + /** + * This is a convenience method for adding a property to a class. + * + */ + public void addProperty(TypeProperty rdfsProperty) + { + List domainIncludes = rdfsProperty.getDomainIncludes(); + if (domainIncludes == null) + { + domainIncludes = new ArrayList<>(); + rdfsProperty.setDomainIncludes(domainIncludes); + } + if (id == null) + { + throw new IllegalArgumentException("Class id is null"); + } + domainIncludes.add(id); + } + + public void setId(String id) + { + this.id = id; + } + + public String getType() + { + return "rdfs:Class"; + } + + public void setType(String type) + { + this.type = type; + } + + public void setSubClassOf(List subClassOf) + { + this.subClassOf = subClassOf; + } + + public void setOntologicalAnnotations(List ontologicalAnnotations) + { + this.ontologicalAnnotations = ontologicalAnnotations; + } +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Restriction.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Restriction.java new file mode 100644 index 0000000..6f7e9e1 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Restriction.java @@ -0,0 +1,48 @@ +package ch.eth.sis.rocrate.facade; + +public class Restriction implements IRestriction +{ + + String id; + + + IPropertyType propertyType; + + int minCardinality; + + int maxCardinality; + + public Restriction(String id, IPropertyType propertyType, int minCardinality, + int maxCardinality) + { + this.id = id; + this.propertyType = propertyType; + this.minCardinality = minCardinality; + this.maxCardinality = maxCardinality; + } + + @Override + public String getId() + { + return id; + } + + @Override + public IPropertyType getPropertyType() + { + return propertyType; + } + + @Override + public int getMinCardinality() + { + return minCardinality; + } + + @Override + public int getMaxCardinality() + { + return maxCardinality; + } + +} diff --git a/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Type.java b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Type.java new file mode 100644 index 0000000..b350e58 --- /dev/null +++ b/0.2.x/lib/java/src/java/ch/ethz/sis/rocrate/facade/Type.java @@ -0,0 +1,144 @@ +package ch.eth.sis.rocrate.facade; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class Type implements IType +{ + String id; + + String type; + + List subClassOf; + + List ontologicalAnnotations; + + List rdfsProperties; + + String comment; + + String label; + + List restrictions; + + public Type() + { + this.subClassOf = new ArrayList<>(); + this.ontologicalAnnotations = new ArrayList<>(); + this.rdfsProperties = new ArrayList<>(); + this.restrictions = new ArrayList<>(); + + } + + public String getId() + { + return id; + } + + @Override + public List getSubClassOf() + { + return subClassOf; + } + + @Override + public List getOntologicalAnnotations() + { + return ontologicalAnnotations; + } + + @Override + public String getComment() + { + return null; + } + + @Override + public String getLabel() + { + return null; + } + + @Override + public List getResstrictions() + { + return restrictions; + } + + /** + * This is a convenience method for adding a property to a class. + * + */ + public void addProperty(PropertyType rdfsProperty) + { + List domainIncludes = rdfsProperty.getDomainIncludes(); + if (domainIncludes == null) + { + domainIncludes = new ArrayList<>(); + rdfsProperty.setDomainIncludes(domainIncludes); + } + if (id == null) + { + throw new IllegalArgumentException("Class id is null"); + } + domainIncludes.add(this); + } + + public void setId(String id) + { + this.id = id; + } + + public String getType() + { + return "rdfs:Class"; + } + + public void setType(String type) + { + this.type = type; + } + + public void setSubClassOf(List subClassOf) + { + this.subClassOf = subClassOf; + } + + public void setOntologicalAnnotations(List ontologicalAnnotations) + { + this.ontologicalAnnotations = ontologicalAnnotations; + } + + public void setComment(String comment) + { + this.comment = comment; + } + + public void setLabel(String label) + { + this.label = label; + } + + public void addRestriction(IRestriction restriction) + { + this.restrictions.add(restriction); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Type type = (Type) o; + return Objects.equals(id, type.id); + } + + @Override + public int hashCode() + { + return Objects.hash(id); + } +} diff --git a/0.2.x/spec.md b/0.2.x/spec.md new file mode 100644 index 0000000..9d31717 --- /dev/null +++ b/0.2.x/spec.md @@ -0,0 +1,409 @@ +# Profile/Module : RO-Crate Interoperability Profile + +AKA: Convention to include schemas and metadata inside `ro-crate-metadata.json` + +**Index:** + +### ro-crate-metadata.json Convention +- [Version](#version) +- [What's new?](#whats-new) +- [Definitions](#definitions) +- [Background](#background) +- [Goals](#goals) +- [Technologies and Usage](#technologies-and-usage) + - [Schema Representation](#schema-representation) + - [RDFS Class](#rdfs-class) + - [RDFS Property](#rdfs-class) + - [Metadata Representation](#metadata-representation) + - [RDF Metadata Entry](#rdf-metadata-entry) +- [Reference Examples](#reference-examples-for-both-schema-and-entries) + +### Reference API +- [API](#api) + - [Schema Representation DTOs](#schema-representation-dtos) + - [Metadata Representation DTOs](#metadata-representation-dtos) + - [Additional RO-Crate API Methods](#additional-ro-crate-api-methods) +- [Primitive Data Types](#primitive-data-types) +- [API Reference Implementation in Java](#api-reference-implementation-in-java) +- [API Reference Examples in Java](#api-reference-examples-in-java) + +### Organizational Information +- [Ongoing Work](#ongoing-work) +- [Possible Future Directions](#possible-future-directions) +- [People](#people) + +# Changelog + +### 0.2.0, compatible with RO-Crate 1.1 + +#### What's new? + +- Cardinalities [RDFS Class](#rdfs-class), [OWl Restriction](#owl-restriction) +- Mandatory properties [RDFS Class](#rdfs-class), [OWl Restriction](#owl-restriction) +- Labels [RDFS Property](#rdfs-class), [RDFS Class](#rdfs-class) +- Human-readable comments[RDFS Property](#rdfs-class), [RDFS Class](#rdfs-class) +- Intersection types [RDF Metadata Entry](#rdf-metadata-entry) +- Better description of primitive data types [Primitive Data Types](#primitive-data-types) +- Ranges of properties are now built using this library's IType and LiteralType +- Updated future plans! [Possible Future Directions](#possible-future-directions) +- Background section [Background](#background) + +### 0.1.0, compatible with RO-Crate 1.1 + +- Initial version + +# Definitions + +We use the following definitions in our proposal. + +- Schema: A logical design that defines the structure, organization and relationship between data. +- Metadata: data of a database adhering to the schema. +- Ontology: A set of concepts and the relationships between these concepts. + +# Background + + +This convention has its roots in interoperability projects involving established electronic lab notebooks and data repositories. +For historical reasons, schemas for metadata may differ between systems, even if they cover similar concepts. + +# Goals + +This proposal SHOULD allow the means to exchange a database schema and database contents in a +standardized way. + +As consequence, Integrations SHOULD NOT need to parse individual files in non-standardized formats +anymore to obtain such information but MAY use the Ro-Crate API for such purpose. + +Since the goal is that multiple established systems can adhere to it, this poses the +additional problem that are multiple schemas in use for similar concepts. +To address this, we propose a way to annotate our schemas with ontological information. +The ontologies allow identification of shared concepts. +Knowing which concepts are shared allows easier integration for different schemas. + +Establishing such a format for interoperability would also benefit independent interoperability +efforts, as they would be available for reuse in other interoperability projects. + +This specification is made to be usable in Ro-Crate 1.1, as such: +- It SHOULD NOT add new keywords. +- It SHOULD establish a convention that can be used by the RO-Crate API to read/write the information. + +# Technologies and Usage + +- [RDF](https://www.w3.org/RDF/): Resource Description Framework is a specification developed by the + World Wide Web + Consortium (W3C) to provide a framework for representing and exchanging data on the web in a + structured way. RDF allows information to be described in terms of subject-predicate-object + triples, which form a graph of interconnected data. RDF can be serialized in different formats, + including JSON-LD as used by RO-Crate. +- [RDFS](https://www.w3.org/TR/rdf-schema/): Resource Description Framework Schema is a + specification developed by the World Wide Web Consortium (W3C) that extends RDF (Resource + Description Framework). RDFS provides a way to define the structure and relationships of RDF data, + allowing for the creation of vocabularies and the specification of classes, properties, and + hierarchies in an RDF dataset. +- [OWL](https://www.w3.org/OWL/): Web Ontology Language is a formal language used to define and + represent ontologies on the web. +- [XSD](https://www.w3.org/TR/xmlschema11-1/): XML Schema Definition is a language used to define + the structure, content, and constraints of XML documents. It will be used in this specification to + express primitive type. + +## Schema Representation +Because the schema is graph-based this can be easily integrated into the RO-Crate graph. + +The schema could also be included in a separate file in a future version of this specification. + +Ontologies are added using OWL's `equivalentClass` and `equivalentProperty` properties. + +What are the advantages of this? + +- the format is backward compatible +- this only uses features that RO-Crate already provides, no additional keywords are required +- Common format for export that prevents `n * (n - 1)` integration situation +- Thorough description of metadata, better automated checking and read-in + +**Formal description:** + +RO-Crate MUST include a graph description of the schema. +This is expressed using 2 types: + +- RDFS Class +- RDFS Property + +### RDFS Class + +Based on RDFS classes, these can be used as object and subjects of triples. + +| Type/Property | Required? | Description | +|---------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| @id | MUST | ID of the entry | +| @type | MUST | Is `rdfs:Class` | +| owl:equivalentClass | MAY | Ontological annotation https://www.w3.org/TR/owl-ref/#equivalentClass-def | +| rdfs:subClassOf | MUST | Used to indicate inheritance. Each entry has to inherit from something, this can be a base type. https://www.w3.org/TR/rdf-schema/#ch_subclassof | +| rdfs:label | MAY | Label of the class | +| rdfs:comment | MAY | Human-readable description of this class | +| owl:restriction | MAY | OWL restriction, a list of OWL restrictions, see [OWl Restriction](#owl-restriction) | + + +### OWL Restriction + +These represent restrictions on properties. At the moment, they encode cardinalities. +Cardinalities with a max of 0 and a min of 0 can be omitted. +A max cardinality of `0` represents an arbitrary, potentially infinite number of values. + +| Type/Property | Required? | Description | +|--------------------|-----------|------------------------------------------------------------------------------| +| @id | MUST | ID of the entry | +| @type | MUST | Is `owl:Restriction` | +| owl:onProperty | MUST | Describes the property tye his restriction belongs to | +| owl:minCardinality | MAY | Indicates whether a property is mandatory (1) or not (0). | +| owl:maxCardinality | MAY | Indicates whether a property may have multiple values (0) or only one (1). | + +### RDFS Property + +RDFS Properties, these represent predicates in triples. +They also specify, which classes they can interact with. + +| Type/Property | Required? | Description | +|------------------------|-----------|---------------------------------------------------------------------------------------------------------| +| @id | MUST | ID of the entry | +| @type | MUST | Is `rdfs:Property` | +| owl:equivalentProperty | MAY | Ontological annotation https://www.w3.org/TR/owl-ref/#equivalentClass-def | +| schema:domainIncludes | MUST | Describes the possible types of the subject. This can be one or many. | +| schema:rangeIncludes | MUST | Describes the possible types of the object. This can be one or many. | +| rdfs:label | MAY | Label of the property | +| rdfs:comment | MAY | Human-readable description of the property | + + +## Metadata Representation + +**Formal description:** + +RO-Crate MUST include a graph description of the metadata entries. +This is expressed using 1 type: + +- Metadata Entry + +### RDF Metadata Entry + +A metadata entry, described by a RDFS class. + +| Type/Property | Required? | Description | +|---------------|-----------|----------------------------------------------------| +| @id | MUST | ID of the entry | +| @type | MUST | Type of the entry, MUST be at least one RDFS Class | + +Further properties are included as specified in the RDFS description as fields. + +# Reference Examples for both Schema and Entries + +We created a small example. It can be found under: +`./examples/ro-crate-1.1/ro-crate-metadata/ro-crate-metadata.json.` +This describes the export +of `./examples/reference-openbis-export`. + +# API + +**Formal description:** + +To be general, the API uses a lot of strings. This allows flexibility in the classes being used. + +The interfaces are shown using Java since is a statically typed language, but they can be +implemented in most languages, +including Python and Javascript. + +## Schema Representation DTOs + +```Java + +/* Represents a class, if we are talking about a schema, it is closely related with the definition of a table or type */ +interface IType +{ + + /* Returns the ID of this type */ + String getId(); + + /* Returns IDs of the types this type inherits from */ + List getSubClassOf(); + + /* Returns the ontological annotations of this type */ + List getOntologicalAnnotations(); + + /* Returns a human-readable description of this type */ + String getComment(); + + /* Returns a human-readable label of this type */ + String getLabel(); + + /* Get Restrictions placed on the properties of this type */ + List getResstrictions(); + +} + +/* Represents a property in a graph, if we are talking about a schema, is closely related with a table column or type property */ +interface IPropertyType +{ + + /* Returns the ID of this property type */ + String getId(); + + /* Return possible values for the subject of this property type */ + List getDomain(); + + /* Return possible values for the object of this property type */ + List getRange(); + + /* Returns the ontological annotations of this property type */ + List getOntologicalAnnotations(); + + /* Returns whether this property has a min cardinality. 0 means optional, 1 means mandatory. */ + int getMinCardinality(); + + /* Returns whether this property has a max cardinality. 0 means many values possible, 1 means only one is possible. */ + int getMaxCardinality(); + + /* Returns a human-readable description of this type */ + String getComment(); + + /* Returns a human-readable label of this type */ + String getLabel(); + + + } +``` + +## Metadata Representation DTOs + +```Java +/* Represents a metadata entity. It is described */ +interface IMetadataEntry +{ + + + /** + * Returns the ID of this entry + */ + String getId(); + + /* Returns the type ID of this entry */ + String getClassId(); + + /* These are key-value pairs for serialization. These are single-valued. + * Serializable classes are: String, Number and Boolean */ + Map getValues(); + + /* These are references to other objects in the graph. + * Each key may have one or more references */ + Map> getReferences(); +} +``` + +## Additional RO-Crate API Methods + + +```Java +/* The API to program against, this wraps around existing RO-Crate APIs. */ +interface ISchemaFacade +{ + + /* Get the crate being worked on */ + RoCrate getCrate(); + + /* Adds a single class */ + void addType(IType rdfsClass); + + /** Retrieves all Classes */ + List getTypes(); + + /* Get a single type by its ID */ + IType getTypes(String id); + + /* Adds a single property */ + void addPropertyType(IPropertyType property); + + /* Get all Properties */ + List getPropertyTypes(); + + /* Gets a single property by its ID. */ + IPropertyType getPropertyType(String id); + + /* Add a single metadata entry */ + void addEntry(IMetadataEntry entry); + + /* Get a single metadata entry by its ID */ + IMetadataEntry getEntry(String id); + + /* Get all metadata entities */ + List getEntries(String rdfsClassId); + +} +``` + +# Primitive Data Types + +The following types from xsd are supported + + +| serialized as | in library | Usage | +|----------------|--------------|-------------------------------| +| xsd:integer | INTEGER | Any length of integer | +| xsd:float | FLOAT | 32-bit floating point number | +| xsd:double | DOUBLE | 64-bit floating point number | +| xsd:decimal | DECIMAL | Arbitrary size decimal number | +| xsd:float | FLOAT | 32-bit floating point number | +| xsd:datetime | DATETIME | Datetime | +| xsd:string | STRING | String | +| rdf:XMLLiteral | DATETIME | XML | + + + + + + +# API Reference Implementation in Java + +A working implementation of the API for Java (source and compiled) can be found +under: `./lib/src`. + +A compiled jar can be found under: `./lib/java/bin`. +The dependencies are specified in the module's `build.gradle` +file: `./lib/java/src/build.gradle`. + +# API Reference Examples in Java + +Working examples of the API in java to read and write can be found +at: `./`, specifically the class +files + +- `./lib/java/src/java/ch/eth/sis/rocrate/example/ReadExample.java` +- `./lib/java/src/java/ch/eth/sis/rocrate/example/WriteExample.java` + + + +# Ongoing Work / Future Plans + +## More serialization formats + +We are planning to investigate different formats for serializing schema, metadata and ontological annotations. + +## Including reference ontologies inside the RO-Crate + +One issue is that ontologies in RO-Crate are links. These can be subject to link rot. +To address this, reference ontologies are included in the RO-Crate. This helps interpretability and allows it to be shared more easily. +Think of it as docker for data! + +## Separate files for schemas + +At the moment, the graph inside the RO-Crate manifest can become large, unwieldy even. +Moving this into one or more separate files and referencing them in the manifest should keep things more manageable. + + + +## Resolve schema.org types automagically + +This allows using the schema.org without explicitly defining them in the schema. +It makes it easier to use the convention that is common in RO-Crate. + + +# People + +- Andreas Meier (andreas.meier@ethz.ch) +- Juan Fuentes (juan.fuentes@id.ethz.ch) diff --git a/README.md b/README.md new file mode 100644 index 0000000..68a0da3 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Ro-Crate SIS Specifications Directory + + +## Purpose + +This repository contains Profiles/Modules specifications to use with RO-Crate. + +It serves as a permanent URL for these specifications. + +## Structure + +Each specification is in a separate directory. + +``` +/specification-name/ +``` + +This directory contains different versions, each identified by a directory names. + +``` +/specification-name/A.B.x/ +``` + +Each version contains a specification file, `spec.md`. + +The specification file indicates which RO-Crate versions it is compatible with. + +PATCH versions are contained in the same directory, PATCHES are intended to fix issues that do not change the workings of the specification, e.g. typos. + +Aside from this, the specifications are not changed after release. + +``` +/specification-name/A.B.x/spec.md +``` + +It may also contain library code in both source and compiled forms, if applicable. + +``` +/specification-name/A.B.x/spec.md/lib/ +``` \ No newline at end of file