From 80f012f9aade08ade032901e5e6e3c8113ef127a Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 16 Sep 2021 09:48:49 +0200 Subject: [PATCH 01/28] Refactor of package io: create empty shells to be filled. --- .gitignore | 1 + .../wot/td/io/AbstractTDWriter.java | 33 ++ .../ics/interactions/wot/td/io/TDWriter.java | 24 + .../wot/td/io/{ => graph}/ReadWriteUtils.java | 20 +- .../td/io/{ => graph}/SchemaGraphReader.java | 81 +-- .../td/io/{ => graph}/SchemaGraphWriter.java | 50 +- .../wot/td/io/{ => graph}/TDGraphReader.java | 180 +++---- .../wot/td/io/{ => graph}/TDGraphWriter.java | 173 +++---- .../wot/td/io/json/SchemaJsonReader.java | 4 + .../wot/td/io/json/SchemaJsonWriter.java | 4 + .../wot/td/io/json/TDJsonReader.java | 5 + .../wot/td/io/json/TDJsonWriter.java | 59 +++ .../wot/td/clients/TDHttpRequestTest.java | 196 +++---- .../io/{ => graph}/SchemaGraphReaderTest.java | 160 +++--- .../io/{ => graph}/SchemaGraphWriterTest.java | 168 +++--- .../td/io/{ => graph}/TDGraphReaderTest.java | 478 +++++++++--------- .../td/io/{ => graph}/TDGraphWriterTest.java | 258 +++++----- 17 files changed, 1015 insertions(+), 879 deletions(-) create mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/AbstractTDWriter.java create mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/TDWriter.java rename src/main/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/ReadWriteUtils.java (92%) rename src/main/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/SchemaGraphReader.java (95%) rename src/main/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/SchemaGraphWriter.java (97%) rename src/main/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/TDGraphReader.java (92%) rename src/main/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/TDGraphWriter.java (81%) create mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java create mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java create mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java create mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java rename src/test/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/SchemaGraphReaderTest.java (91%) rename src/test/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/SchemaGraphWriterTest.java (82%) rename src/test/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/TDGraphReaderTest.java (76%) rename src/test/java/ch/unisg/ics/interactions/wot/td/io/{ => graph}/TDGraphWriterTest.java (84%) diff --git a/.gitignore b/.gitignore index bf721ea6..3f08db50 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ bin/ .DS_Store .classpath .project +.idea # Ignore Gradle GUI config gradle-app.setting diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/AbstractTDWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/AbstractTDWriter.java new file mode 100644 index 00000000..99d5274b --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/AbstractTDWriter.java @@ -0,0 +1,33 @@ +package ch.unisg.ics.interactions.wot.td.io; + +import ch.unisg.ics.interactions.wot.td.ThingDescription; + +/** + * A writer for serializing TDs. + * Provides a fluent API for adding prefix bindings to be used for serialization. + */ +public abstract class AbstractTDWriter implements TDWriter { + + protected final ThingDescription td; + + protected AbstractTDWriter(ThingDescription td) { + this.td = td; + } + + protected abstract AbstractTDWriter setNamespace(String prefix, String namespace); + + protected abstract AbstractTDWriter addTypes(); + + protected abstract AbstractTDWriter addTitle(); + + protected abstract AbstractTDWriter addSecurity(); + + protected abstract AbstractTDWriter addBaseURI(); + + protected abstract AbstractTDWriter addProperties(); + + protected abstract AbstractTDWriter addActions(); + + protected abstract AbstractTDWriter addGraph(); + +} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/TDWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/TDWriter.java new file mode 100644 index 00000000..a26d6074 --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/TDWriter.java @@ -0,0 +1,24 @@ +package ch.unisg.ics.interactions.wot.td.io; + +import ch.unisg.ics.interactions.wot.td.ThingDescription; +import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphWriter; +import ch.unisg.ics.interactions.wot.td.io.json.TDJsonWriter; +import org.eclipse.rdf4j.rio.RDFFormat; + +public interface TDWriter { + + /** + * Writes out as a String the Thing Description associated with this writer. + * @return a string representing the Thing Description in the correct format. + */ + String write(); + + //TODO this should not be dependent from RDFFormat + static String write(ThingDescription td, RDFFormat format) { + if(format.equals(RDFFormat.JSONLD) || format.equals(RDFFormat.NDJSONLD)) { + return new TDJsonWriter(td).write(); + } + return new TDGraphWriter(td).write(); + } + +} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/ReadWriteUtils.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/ReadWriteUtils.java similarity index 92% rename from src/main/java/ch/unisg/ics/interactions/wot/td/io/ReadWriteUtils.java rename to src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/ReadWriteUtils.java index 5b42b909..2578518a 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/ReadWriteUtils.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/ReadWriteUtils.java @@ -1,4 +1,4 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -21,24 +21,24 @@ final class ReadWriteUtils { private final static Logger LOGGER = Logger.getLogger(ReadWriteUtils.class.getCanonicalName()); - static Model readModelFromString(RDFFormat format, String description, String baseURI) + static Model readModelFromString(RDFFormat format, String description, String baseURI) throws RDFParseException, RDFHandlerException, IOException { StringReader stringReader = new StringReader(description); - + RDFParser rdfParser = Rio.createParser(RDFFormat.TURTLE); Model model = new LinkedHashModel(); rdfParser.setRDFHandler(new StatementCollector(model)); - + rdfParser.parse(stringReader, baseURI); - + return model; } - + static String writeToString(RDFFormat format, Model model) { OutputStream out = new ByteArrayOutputStream(); - + try { - Rio.write(model, out, format, + Rio.write(model, out, format, new WriterConfig().set(BasicWriterSettings.INLINE_BLANK_NODES, true)); } finally { try { @@ -47,9 +47,9 @@ static String writeToString(RDFFormat format, Model model) { LOGGER.log(Level.WARNING, e.getMessage()); } } - + return out.toString(); } - + private ReadWriteUtils() { } } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphReader.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphReader.java similarity index 95% rename from src/main/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphReader.java rename to src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphReader.java index 275bc28e..e96cdbd8 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphReader.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphReader.java @@ -1,9 +1,10 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import ch.unisg.ics.interactions.wot.td.io.InvalidTDException; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Model; @@ -26,19 +27,19 @@ class SchemaGraphReader { private final Model model; private final ValueFactory rdf = SimpleValueFactory.getInstance(); - + SchemaGraphReader(Model model) { this.model = model; } - + static Optional readDataSchema(Resource nodeId, Model model) { SchemaGraphReader reader = new SchemaGraphReader(model); return reader.readDataSchema(nodeId); } - + private Optional readDataSchema(Resource schemaId) { Set types = Models.objectIRIs(model.filter(schemaId, RDF.TYPE, null)); - + if (!types.isEmpty()) { if (types.contains(rdf.createIRI(JSONSchema.ObjectSchema))) { return readObjectSchema(schemaId); @@ -62,22 +63,22 @@ private Optional readDataSchema(Resource schemaId) { return Optional.of(builder.build()); } } - + return Optional.empty(); } - + private Optional readObjectSchema(Resource schemaId) { ObjectSchema.Builder builder = new ObjectSchema.Builder(); readDataSchemaMetadata(builder, schemaId); - + /* Read properties */ - Set propertyIds = Models.objectResources(model.filter(schemaId, + Set propertyIds = Models.objectResources(model.filter(schemaId, rdf.createIRI(JSONSchema.properties), null)); for (Resource property : propertyIds) { Optional propertySchema = readDataSchema(property); if (propertySchema.isPresent()) { // Each property of an object should also have an associated property name - Optional propertyName = Models.objectLiteral(model.filter(property, + Optional propertyName = Models.objectLiteral(model.filter(property, rdf.createIRI(JSONSchema.propertyName), null)); if (!propertyName.isPresent()) { throw new InvalidTDException("ObjectSchema property is missing a property name."); @@ -85,37 +86,37 @@ private Optional readObjectSchema(Resource schemaId) { builder.addProperty(propertyName.get().stringValue(), propertySchema.get()); } } - + /* Read required properties */ - Set requiredProperties = Models.objectLiterals(model.filter(schemaId, + Set requiredProperties = Models.objectLiterals(model.filter(schemaId, rdf.createIRI(JSONSchema.required), null)); for (Literal requiredProp : requiredProperties) { builder.addRequiredProperties(requiredProp.stringValue()); } - + return Optional.of(builder.build()); } - + private Optional readArraySchema(Resource schemaId) { ArraySchema.Builder builder = new ArraySchema.Builder(); readDataSchemaMetadata(builder, schemaId); - + /* Read minItems */ - Optional minItems = Models.objectLiteral(model.filter(schemaId, + Optional minItems = Models.objectLiteral(model.filter(schemaId, rdf.createIRI(JSONSchema.minItems), null)); if (minItems.isPresent()) { builder.addMinItems(minItems.get().intValue()); } - + /* Read maxItems */ - Optional maxItems = Models.objectLiteral(model.filter(schemaId, + Optional maxItems = Models.objectLiteral(model.filter(schemaId, rdf.createIRI(JSONSchema.maxItems), null)); if (maxItems.isPresent()) { builder.addMaxItems(maxItems.get().intValue()); } - + /* Read items */ - Set itemIds = Models.objectResources(model.filter(schemaId, + Set itemIds = Models.objectResources(model.filter(schemaId, rdf.createIRI(JSONSchema.items), null)); for (Resource itemId : itemIds) { Optional item = readDataSchema(itemId); @@ -123,65 +124,65 @@ private Optional readArraySchema(Resource schemaId) { builder.addItem(item.get()); } } - + return Optional.of(builder.build()); } - + private Optional readIntegerSchema(Resource schemaId) { IntegerSchema.Builder builder = new IntegerSchema.Builder(); - + readDataSchemaMetadata(builder, schemaId); - - Optional maximum = Models.objectLiteral(model.filter(schemaId, + + Optional maximum = Models.objectLiteral(model.filter(schemaId, rdf.createIRI(JSONSchema.maximum), null)); if (maximum.isPresent()) { builder.addMaximum(maximum.get().intValue()); } - - Optional minimum = Models.objectLiteral(model.filter(schemaId, + + Optional minimum = Models.objectLiteral(model.filter(schemaId, rdf.createIRI(JSONSchema.minimum), null)); if (minimum.isPresent()) { builder.addMinimum(minimum.get().intValue()); } - + return Optional.of(builder.build()); } - + private Optional readNumberSchema(Resource schemaId) { NumberSchema.Builder builder = new NumberSchema.Builder(); - + readDataSchemaMetadata(builder, schemaId); - - Optional maximum = Models.objectLiteral(model.filter(schemaId, + + Optional maximum = Models.objectLiteral(model.filter(schemaId, rdf.createIRI(JSONSchema.maximum), null)); if (maximum.isPresent()) { builder.addMaximum(maximum.get().doubleValue()); } - - Optional minimum = Models.objectLiteral(model.filter(schemaId, + + Optional minimum = Models.objectLiteral(model.filter(schemaId, rdf.createIRI(JSONSchema.minimum), null)); if (minimum.isPresent()) { builder.addMinimum(minimum.get().doubleValue()); } - + return Optional.of(builder.build()); } - + @SuppressWarnings({ "rawtypes", "unchecked" }) private void readDataSchemaMetadata(DataSchema.Builder builder, Resource schemaId) { /* Read semantic types (IRIs) */ Set semIRIs = Models.objectIRIs(model.filter(schemaId, RDF.TYPE, null)); builder.addSemanticTypes(semIRIs.stream().map(iri -> iri.stringValue()) .collect(Collectors.toSet())); - + /* Read semantic types (strings) */ Set semTags = Models.objectStrings(model.filter(schemaId, RDF.TYPE, null)); builder.addSemanticTypes(semTags); - + /* Read enumeration */ - Set enumeration = Models.objectStrings(model.filter(schemaId, + Set enumeration = Models.objectStrings(model.filter(schemaId, rdf.createIRI(JSONSchema.enumeration), null)); builder.addEnum(enumeration); } - + } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphWriter.java similarity index 97% rename from src/main/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphWriter.java rename to src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphWriter.java index f6274bcc..52ad3948 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphWriter.java @@ -1,4 +1,4 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import java.util.Map; import java.util.Set; @@ -21,20 +21,20 @@ class SchemaGraphWriter { private final static Logger LOGGER = Logger.getLogger(SchemaGraphWriter.class.getCanonicalName()); - + private final ModelBuilder graphBuilder; private final ValueFactory rdf = SimpleValueFactory.getInstance(); - - + + SchemaGraphWriter(ModelBuilder builder) { this.graphBuilder = builder; } - + static void write(ModelBuilder builder, Resource nodeId, DataSchema schema) { SchemaGraphWriter writer = new SchemaGraphWriter(builder); writer.addDataSchema(nodeId, schema); } - + private void addDataSchema(Resource nodeId, DataSchema schema) { switch (schema.getDatatype()) { case DataSchema.OBJECT: @@ -61,60 +61,60 @@ private void addDataSchema(Resource nodeId, DataSchema schema) { break; } } - + private void addDataSchemaMetadata(Resource nodeId, DataSchema schema) { addObjectIRIs(nodeId, RDF.TYPE, schema.getSemanticTypes()); addObjectIRIs(nodeId, rdf.createIRI(JSONSchema.enumeration), schema.getEnumeration()); } - + private void addObjectSchema(Resource nodeId, ObjectSchema schema) { graphBuilder.add(nodeId, RDF.TYPE, rdf.createIRI(JSONSchema.ObjectSchema)); addDataSchemaMetadata(nodeId, schema); - + /* Add object properties */ Map properties = schema.getProperties(); - + for (String propertyName : properties.keySet()) { Resource propertyId = rdf.createBNode(); - + graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.properties), propertyId); graphBuilder.add(propertyId, rdf.createIRI(JSONSchema.propertyName), propertyName); - + addDataSchema(propertyId, properties.get(propertyName)); } - + /* Add names of required properties */ for (String required : schema.getRequiredProperties()) { graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.required), required); } } - + private void addArraySchema(Resource nodeId, ArraySchema schema) { graphBuilder.add(nodeId, RDF.TYPE, rdf.createIRI(JSONSchema.ArraySchema)); addDataSchemaMetadata(nodeId, schema); - + if (schema.getMinItems().isPresent()) { - graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.minItems), + graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.minItems), schema.getMinItems().get().intValue()); } - + if (schema.getMaxItems().isPresent()) { graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.maxItems), schema.getMaxItems().get() .intValue()); } - + for (DataSchema item : schema.getItems()) { BNode itemId = rdf.createBNode(); graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.items), itemId); addDataSchema(itemId, item); } } - + private void addSimpleSchema(Resource nodeId, DataSchema schema, IRI schemaType) { graphBuilder.add(nodeId, RDF.TYPE, schemaType); addDataSchemaMetadata(nodeId, schema); } - + private void addNumberSchema(Resource nodeId, NumberSchema numberSchema) { if (numberSchema.getDatatype().equals(DataSchema.INTEGER)) { graphBuilder.add(nodeId, RDF.TYPE, rdf.createIRI(JSONSchema.IntegerSchema)); @@ -122,26 +122,26 @@ private void addNumberSchema(Resource nodeId, NumberSchema numberSchema) { graphBuilder.add(nodeId, RDF.TYPE, rdf.createIRI(JSONSchema.NumberSchema)); } addDataSchemaMetadata(nodeId, numberSchema); - + if (numberSchema.getMinimum().isPresent()) { if (numberSchema.getDatatype().equals(DataSchema.INTEGER)) { - graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.minimum), + graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.minimum), ((IntegerSchema) numberSchema).getMinimumAsInteger().get()); } else { graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.minimum), numberSchema.getMinimum().get()); } } - + if (numberSchema.getMaximum().isPresent()) { if (numberSchema.getDatatype().equals(DataSchema.INTEGER)) { - graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.maximum), + graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.maximum), ((IntegerSchema) numberSchema).getMaximumAsInteger().get()); } else { graphBuilder.add(nodeId, rdf.createIRI(JSONSchema.maximum), numberSchema.getMaximum().get()); } } } - + private void addObjectIRIs(Resource nodeId, IRI property, Set objects) { for (String type : objects) { try { diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/TDGraphReader.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReader.java similarity index 92% rename from src/main/java/ch/unisg/ics/interactions/wot/td/io/TDGraphReader.java rename to src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReader.java index 2d71187b..daedb5bb 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/TDGraphReader.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReader.java @@ -1,4 +1,4 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import java.io.IOException; import java.io.StringReader; @@ -11,6 +11,7 @@ import java.util.Set; import java.util.stream.Collectors; +import ch.unisg.ics.interactions.wot.td.io.InvalidTDException; import org.apache.hc.client5.http.fluent.Request; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Literal; @@ -43,7 +44,7 @@ /** * A reader for deserializing TDs from RDF representations. The created ThingDescription - * maintains the full RDF graph read as input, which can be retrieved with the getGraph + * maintains the full RDF graph read as input, which can be retrieved with the getGraph * method. * */ @@ -51,78 +52,79 @@ public class TDGraphReader { private final Resource thingId; private Model model; private final ValueFactory rdf = SimpleValueFactory.getInstance(); - + public static ThingDescription readFromURL(TDFormat format, String url) throws IOException { String representation = Request.get(url).execute().returnContent().asString(); return readFromString(format, representation); } - + /** * Returns a ThingDescription object based on the path parameter that points to a file. Should the path be invalid * or if the file does not exist, an IOException is thrown. - * + * * @param format the file's thing description * @param path the location of the file that contains the thing description * @return the thing description + * @throws IOException when the file is not read correctly */ public static ThingDescription readFromFile(TDFormat format, String path) throws IOException { String content = new String(Files.readAllBytes(Paths.get(path))); return readFromString(format, content); } - + public static ThingDescription readFromString(TDFormat format, String representation) { TDGraphReader reader; - + if (format == TDFormat.RDF_TURTLE) { reader = new TDGraphReader(RDFFormat.TURTLE, representation); } else { reader = new TDGraphReader(RDFFormat.JSONLD, representation); } - + ThingDescription.Builder tdBuilder = new ThingDescription.Builder(reader.readThingTitle()) .addSemanticTypes(reader.readThingTypes()) .addSecuritySchemes(reader.readSecuritySchemes()) .addProperties(reader.readProperties()) .addActions(reader.readActions()) .addGraph(reader.getGraph()); - + Optional thingURI = reader.getThingURI(); if (thingURI.isPresent()) { tdBuilder.addThingURI(thingURI.get()); } - + Optional base = reader.readBaseURI(); if (base.isPresent()) { tdBuilder.addBaseURI(base.get()); } - + return tdBuilder.build(); } - + TDGraphReader(RDFFormat format, String representation) { loadModel(format, representation, ""); - + Optional baseURI = readBaseURI(); if (baseURI.isPresent()) { loadModel(format, representation, baseURI.get()); } - + try { - thingId = Models.subject(model.filter(null, rdf.createIRI(TD.hasSecurityConfiguration), + thingId = Models.subject(model.filter(null, rdf.createIRI(TD.hasSecurityConfiguration), null)).get(); } catch (NoSuchElementException e) { throw new InvalidTDException("Missing mandatory security definitions.", e); } } - + private void loadModel(RDFFormat format, String representation, String baseURI) { this.model = new LinkedHashModel(); - + RDFParser parser = Rio.createParser(format); parser.setRDFHandler(new StatementCollector(model)); StringReader stringReader = new StringReader(representation); - + try { parser.parse(stringReader, baseURI); } catch (RDFParseException | RDFHandlerException | IOException e) { @@ -131,129 +133,129 @@ private void loadModel(RDFFormat format, String representation, String baseURI) stringReader.close(); } } - + Model getGraph() { return model; } - + Optional getThingURI() { if (thingId instanceof IRI) { return Optional.of(thingId.stringValue()); } - + return Optional.empty(); } - + String readThingTitle() { Literal thingTitle; - + try { - thingTitle = Models.objectLiteral(model.filter(thingId, rdf.createIRI(DCT.title), null)).get(); + thingTitle = Models.objectLiteral(model.filter(thingId, rdf.createIRI(DCT.title), null)).get(); } catch (NoSuchElementException e) { throw new InvalidTDException("Missing mandatory title.", e); } - + return thingTitle.stringValue(); } - + Set readThingTypes() { Set thingTypes = Models.objectIRIs(model.filter(thingId, RDF.TYPE, null)); - + return thingTypes.stream() .map(iri -> iri.stringValue()) .collect(Collectors.toSet()); } - + final Optional readBaseURI() { Optional baseURI = Models.objectIRI(model.filter(thingId, rdf.createIRI(TD.hasBase), null)); - + if (baseURI.isPresent()) { return Optional.of(baseURI.get().stringValue()); } - + return Optional.empty(); } - + List readSecuritySchemes() { - Set nodeIds = Models.objectResources(model.filter(thingId, + Set nodeIds = Models.objectResources(model.filter(thingId, rdf.createIRI(TD.hasSecurityConfiguration), null)); - + List schemes = new ArrayList(); - + for (Resource node : nodeIds) { Optional securityScheme = Models.objectIRI(model.filter(node, RDF.TYPE, null)); - + if (securityScheme.isPresent()) { - Optional scheme = SecurityScheme.fromRDF(securityScheme.get().stringValue(), + Optional scheme = SecurityScheme.fromRDF(securityScheme.get().stringValue(), model, node); - + if (scheme.isPresent()) { schemes.add(scheme.get()); } } } - + return schemes; } - + List readProperties() { List properties = new ArrayList(); - - Set propertyIds = Models.objectResources(model.filter(thingId, + + Set propertyIds = Models.objectResources(model.filter(thingId, rdf.createIRI(TD.hasPropertyAffordance), null)); - + for (Resource propertyId : propertyIds) { try { Optional schema = SchemaGraphReader.readDataSchema(propertyId, model); - + if (schema.isPresent()) { List
forms = readForms(propertyId, InteractionAffordance.PROPERTY); PropertyAffordance.Builder builder = new PropertyAffordance.Builder(schema.get(), forms); - + readAffordanceMetadata(builder, propertyId); - - Optional observable = Models.objectLiteral(model.filter(propertyId, + + Optional observable = Models.objectLiteral(model.filter(propertyId, rdf.createIRI(TD.isObservable), null)); if (observable.isPresent() && observable.get().booleanValue()) { builder.addObserve(); } - + properties.add(builder.build()); } } catch (InvalidTDException e) { throw new InvalidTDException("Invalid property definition.", e); } } - + return properties; } - + List readActions() { List actions = new ArrayList(); - - Set affordanceIds = Models.objectResources(model.filter(thingId, + + Set affordanceIds = Models.objectResources(model.filter(thingId, rdf.createIRI(TD.hasActionAffordance), null)); - + for (Resource affordanceId : affordanceIds) { if (!model.contains(affordanceId, RDF.TYPE, rdf.createIRI(TD.ActionAffordance))) { continue; } - + ActionAffordance action = readAction(affordanceId); actions.add(action); } - + return actions; } - + private ActionAffordance readAction(Resource affordanceId) { List forms = readForms(affordanceId, InteractionAffordance.ACTION); ActionAffordance.Builder actionBuilder = new ActionAffordance.Builder(forms); - + readAffordanceMetadata(actionBuilder, affordanceId); - + try { - Optional inputSchemaId = Models.objectResource(model.filter(affordanceId, + Optional inputSchemaId = Models.objectResource(model.filter(affordanceId, rdf.createIRI(TD.hasInputSchema), null)); if (inputSchemaId.isPresent()) { try { @@ -265,8 +267,8 @@ private ActionAffordance readAction(Resource affordanceId) { throw new InvalidTDException("Invalid action definition.", e); } } - - Optional outSchemaId = Models.objectResource(model.filter(affordanceId, + + Optional outSchemaId = Models.objectResource(model.filter(affordanceId, rdf.createIRI(TD.hasOutputSchema), null)); if (outSchemaId.isPresent()) { Optional output = SchemaGraphReader.readDataSchema(outSchemaId.get(), model); @@ -277,83 +279,83 @@ private ActionAffordance readAction(Resource affordanceId) { } catch (InvalidTDException e) { throw new InvalidTDException("Invalid action definition.", e); } - + return actionBuilder.build(); } - + private void readAffordanceMetadata(InteractionAffordance .Builder> builder, Resource affordanceId) { /* Read semantic types */ Set types = Models.objectIRIs(model.filter(affordanceId, RDF.TYPE, null)); builder.addSemanticTypes(types.stream().map(type -> type.stringValue()) .collect(Collectors.toList())); - + /* Read name */ - Optional name = Models.objectLiteral(model.filter(affordanceId, rdf.createIRI(TD.name), + Optional name = Models.objectLiteral(model.filter(affordanceId, rdf.createIRI(TD.name), null)); if (name.isPresent()) { builder.addName(name.get().stringValue()); } - + /* Read title */ - Optional title = Models.objectLiteral(model.filter(affordanceId, + Optional title = Models.objectLiteral(model.filter(affordanceId, rdf.createIRI(DCT.title), null)); if (title.isPresent()) { builder.addTitle(title.get().stringValue()); } } - + private List readForms(Resource affordanceId, String affordanceType) { List forms = new ArrayList(); - - Set formIdSet = Models.objectResources(model.filter(affordanceId, + + Set formIdSet = Models.objectResources(model.filter(affordanceId, rdf.createIRI(TD.hasForm), null)); - + for (Resource formId : formIdSet) { - Optional targetOpt = Models.objectIRI(model.filter(formId, rdf.createIRI(HCTL.hasTarget), + Optional targetOpt = Models.objectIRI(model.filter(formId, rdf.createIRI(HCTL.hasTarget), null)); - + if (!targetOpt.isPresent()) { continue; } - - Optional methodNameOpt = Models.objectLiteral(model.filter(formId, + + Optional methodNameOpt = Models.objectLiteral(model.filter(formId, rdf.createIRI(HTV.methodName), null)); - - Optional contentTypeOpt = Models.objectLiteral(model.filter(formId, + + Optional contentTypeOpt = Models.objectLiteral(model.filter(formId, rdf.createIRI(HCTL.forContentType), null)); - String contentType = contentTypeOpt.isPresent() ? contentTypeOpt.get().stringValue() + String contentType = contentTypeOpt.isPresent() ? contentTypeOpt.get().stringValue() : "application/json"; - - Optional subprotocolOpt = Models.objectLiteral(model.filter(formId, + + Optional subprotocolOpt = Models.objectLiteral(model.filter(formId, rdf.createIRI(HCTL.forSubProtocol), null)); - - Set opsIRIs = Models.objectIRIs(model.filter(formId, rdf.createIRI(HCTL.hasOperationType), + + Set opsIRIs = Models.objectIRIs(model.filter(formId, rdf.createIRI(HCTL.hasOperationType), null)); Set ops = opsIRIs.stream().map(op -> op.stringValue()).collect(Collectors.toSet()); - + String target = targetOpt.get().stringValue(); - + Form.Builder builder = new Form.Builder(target) .setContentType(contentType) - .addOperationTypes(ops); - + .addOperationTypes(ops); + if (methodNameOpt.isPresent()) { builder.setMethodName(methodNameOpt.get().stringValue()); } - + if (subprotocolOpt.isPresent()) { builder.addSubProtocol(subprotocolOpt.get().stringValue()); } - + forms.add(builder.build()); } - + if (forms.isEmpty()) { throw new InvalidTDException("[" + affordanceType + "] All interaction affordances should have " + "at least one valid."); } - + return forms; } } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/TDGraphWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriter.java similarity index 81% rename from src/main/java/ch/unisg/ics/interactions/wot/td/io/TDGraphWriter.java rename to src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriter.java index c7fc7d54..8ec884f5 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/TDGraphWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriter.java @@ -1,8 +1,9 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import java.util.List; import java.util.Optional; +import ch.unisg.ics.interactions.wot.td.io.AbstractTDWriter; import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Model; @@ -27,181 +28,171 @@ import ch.unisg.ics.interactions.wot.td.vocabularies.TD; /** - * A writer for serializing TDs as RDF graphs. Provides a fluent API for adding prefix bindings to be - * used in the serialization. - * + * A writer for serializing TDs as RDF graphs. */ -public class TDGraphWriter { +public class TDGraphWriter extends AbstractTDWriter { private final Resource thingId; - private final ThingDescription td; private final ModelBuilder graphBuilder; private final ValueFactory rdf = SimpleValueFactory.getInstance(); - + public TDGraphWriter(ThingDescription td) { + super(td); this.thingId = td.getThingURI().isPresent() ? rdf.createIRI(td.getThingURI().get()) : rdf.createBNode(); - - this.td = td; this.graphBuilder = new ModelBuilder(); } - - public static String write(ThingDescription td) { - return new TDGraphWriter(td).write(); + + @Override + public String write() { + return this.addTypes() + .addTitle() + .addSecurity() + .addBaseURI() + .addProperties() + .addActions() + .addGraph() + .write(RDFFormat.TURTLE); } - - /** - * Sets a prefix binding for a given namespace. - * - * @param prefix the prefix to be used in the serialized representation - * @param namespace the given namespace - * @return this TDGraphWriter - */ + + @Override public TDGraphWriter setNamespace(String prefix, String namespace) { this.graphBuilder.setNamespace(prefix, namespace); return this; } - - public String write() { - return this.addTypes() - .addTitle() - .addSecurity() - .addBaseURI() - .addProperties() - .addActions() - .addGraph() - .write(RDFFormat.TURTLE); + + @Override + protected TDGraphWriter addTypes() { + graphBuilder.add(thingId, RDF.TYPE, rdf.createIRI(TD.Thing)); + + for (String type : td.getSemanticTypes()) { + graphBuilder.add(thingId, RDF.TYPE, rdf.createIRI(type)); + } + + return this; } - - private Model getModel() { - return graphBuilder.build(); + + @Override + protected TDGraphWriter addTitle() { + graphBuilder.add(thingId, rdf.createIRI(DCT.title), td.getTitle()); + return this; } - - private TDGraphWriter addSecurity() { + + @Override + protected TDGraphWriter addSecurity() { List securitySchemes = td.getSecuritySchemes(); - + for (SecurityScheme scheme : securitySchemes) { BNode schemeId = rdf.createBNode(); graphBuilder.add(thingId, rdf.createIRI(TD.hasSecurityConfiguration), schemeId); - + Model schemeGraph = scheme.toRDF(schemeId); for (Statement s : schemeGraph) { graphBuilder.add(s.getSubject(), s.getPredicate(), s.getObject()); } } - - return this; - } - - private TDGraphWriter addTypes() { - graphBuilder.add(thingId, RDF.TYPE, rdf.createIRI(TD.Thing)); - - for (String type : td.getSemanticTypes()) { - graphBuilder.add(thingId, RDF.TYPE, rdf.createIRI(type)); - } - - return this; - } - - private TDGraphWriter addTitle() { - graphBuilder.add(thingId, rdf.createIRI(DCT.title), td.getTitle()); + return this; } - - private TDGraphWriter addBaseURI() { + + @Override + protected TDGraphWriter addBaseURI() { if (td.getBaseURI().isPresent()) { - graphBuilder.add(thingId, rdf.createIRI(TD.hasBase), + graphBuilder.add(thingId, rdf.createIRI(TD.hasBase), rdf.createIRI(td.getBaseURI().get())); } - + return this; } - - private TDGraphWriter addProperties() { + + @Override + protected TDGraphWriter addProperties() { for (PropertyAffordance property : td.getProperties()) { Resource propertyId = addAffordance(property, TD.hasPropertyAffordance, TD.PropertyAffordance); graphBuilder.add(propertyId, rdf.createIRI(TD.isObservable), property.isObservable()); - + SchemaGraphWriter.write(graphBuilder, propertyId, property.getDataSchema()); } - + return this; } - - private TDGraphWriter addActions() { + + @Override + protected TDGraphWriter addActions() { for (ActionAffordance action : td.getActions()) { Resource actionId = addAffordance(action, TD.hasActionAffordance, TD.ActionAffordance); - + if (action.getInputSchema().isPresent()) { DataSchema schema = action.getInputSchema().get(); - + Resource inputId = rdf.createBNode(); graphBuilder.add(actionId, rdf.createIRI(TD.hasInputSchema), inputId); - + SchemaGraphWriter.write(graphBuilder, inputId, schema); } - + if (action.getOutputSchema().isPresent()) { DataSchema schema = action.getOutputSchema().get(); - + Resource outputId = rdf.createBNode(); graphBuilder.add(actionId, rdf.createIRI(TD.hasOutputSchema), outputId); - + SchemaGraphWriter.write(graphBuilder, outputId, schema); } } - + return this; } - - private TDGraphWriter addGraph() { + + @Override + protected TDGraphWriter addGraph() { if(td.getGraph().isPresent()) { getModel().addAll(td.getGraph().get()); - + td.getGraph().get().getNamespaces().stream() .filter(ns -> !getModel().getNamespace(ns.getPrefix()).isPresent()) .forEach(graphBuilder::setNamespace); } return this; } - - private Resource addAffordance(InteractionAffordance affordance, String affordanceProp, + + private Resource addAffordance(InteractionAffordance affordance, String affordanceProp, String affordanceClass) { BNode affordanceId = rdf.createBNode(); - + graphBuilder.add(thingId, rdf.createIRI(affordanceProp), affordanceId); graphBuilder.add(affordanceId, RDF.TYPE, rdf.createIRI(affordanceClass)); - + for (String type : affordance.getSemanticTypes()) { graphBuilder.add(affordanceId, RDF.TYPE, rdf.createIRI(type)); } - + if (affordance.getName().isPresent()) { - graphBuilder.add(affordanceId, rdf.createIRI(TD.name), + graphBuilder.add(affordanceId, rdf.createIRI(TD.name), rdf.createLiteral(affordance.getName().get())); } - + if (affordance.getTitle().isPresent()) { graphBuilder.add(affordanceId, rdf.createIRI(DCT.title), affordance.getTitle().get()); } - + addFormsForInteraction(affordanceId, affordance); - + return affordanceId; } - + private void addFormsForInteraction(Resource interactionId, InteractionAffordance interaction) { for (Form form : interaction.getForms()) { BNode formId = rdf.createBNode(); - + graphBuilder.add(interactionId, rdf.createIRI(TD.hasForm), formId); - - // Only writes the method name for forms with one operation type (to avoid ambiguity) + + // Only writes the method name for forms with one operation type (to avoid ambiguity) if (form.getMethodName().isPresent() && form.getOperationTypes().size() == 1) { graphBuilder.add(formId, rdf.createIRI(HTV.methodName), form.getMethodName().get()); } graphBuilder.add(formId, rdf.createIRI(HCTL.hasTarget), rdf.createIRI(form.getTarget())); graphBuilder.add(formId, rdf.createIRI(HCTL.forContentType), form.getContentType()); - + for (String opType : form.getOperationTypes()) { try { IRI opTypeIri = rdf.createIRI(opType); @@ -210,14 +201,18 @@ private void addFormsForInteraction(Resource interactionId, InteractionAffordanc graphBuilder.add(formId, rdf.createIRI(HCTL.hasOperationType), opType); } } - + Optional subprotocol = form.getSubProtocol(); if (subprotocol.isPresent()) { graphBuilder.add(formId, rdf.createIRI(HCTL.forSubProtocol), subprotocol.get()); } } } - + + private Model getModel() { + return graphBuilder.build(); + } + private String write(RDFFormat format) { return ReadWriteUtils.writeToString(format, getModel()); } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java new file mode 100644 index 00000000..410eff3b --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java @@ -0,0 +1,4 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +public class SchemaJsonReader { +} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java new file mode 100644 index 00000000..3ad7354a --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java @@ -0,0 +1,4 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +public class SchemaJsonWriter { +} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java new file mode 100644 index 00000000..a6352350 --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java @@ -0,0 +1,5 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +public class TDJsonReader { + +} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java new file mode 100644 index 00000000..43620493 --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -0,0 +1,59 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +import ch.unisg.ics.interactions.wot.td.ThingDescription; +import ch.unisg.ics.interactions.wot.td.io.AbstractTDWriter; + +/** + * A writer to serialize TDs in the JSON-LD 1.1 format. + */ +public class TDJsonWriter extends AbstractTDWriter { + + public TDJsonWriter(ThingDescription td) { + super(td); + } + + @Override + public String write() { + return null; + } + + @Override + public AbstractTDWriter setNamespace(String prefix, String namespace) { + return null; + } + + @Override + protected AbstractTDWriter addTypes() { + return null; + } + + @Override + protected AbstractTDWriter addTitle() { + return null; + } + + @Override + protected AbstractTDWriter addSecurity() { + return null; + } + + @Override + protected AbstractTDWriter addBaseURI() { + return null; + } + + @Override + protected AbstractTDWriter addProperties() { + return null; + } + + @Override + protected AbstractTDWriter addActions() { + return null; + } + + @Override + protected AbstractTDWriter addGraph() { + return null; + } +} diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/clients/TDHttpRequestTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/clients/TDHttpRequestTest.java index ced53db0..623a1324 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/clients/TDHttpRequestTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/clients/TDHttpRequestTest.java @@ -35,7 +35,7 @@ import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; import ch.unisg.ics.interactions.wot.td.affordances.Form; import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; -import ch.unisg.ics.interactions.wot.td.io.TDGraphReader; +import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphReader; import ch.unisg.ics.interactions.wot.td.schemas.ArraySchema; import ch.unisg.ics.interactions.wot.td.schemas.BooleanSchema; import ch.unisg.ics.interactions.wot.td.schemas.IntegerSchema; @@ -46,7 +46,7 @@ public class TDHttpRequestTest { private static final String PREFIX = "http://example.org/"; - + static final ObjectSchema USER_SCHEMA = new ObjectSchema.Builder() .addSemanticType(PREFIX + "User") .addProperty("first_name", new StringSchema.Builder() @@ -57,76 +57,76 @@ public class TDHttpRequestTest { .build()) .addRequiredProperties("last_name") .build(); - + private static final Form FORM = new Form.Builder(PREFIX + "toggle") .setMethodName("PUT") .addOperationType(TD.invokeAction) .build(); - - private static final String FORKLIFT_ROBOT_TD = "@prefix td: .\n" + - "@prefix htv: .\n" + - "@prefix hctl: .\n" + - "@prefix dct: .\n" + - "@prefix wotsec: .\n" + - "@prefix js: .\n" + - "@prefix ex: .\n" + - "\n" + - "ex:forkliftRobot a td:Thing ; \n" + - " dct:title \"forkliftRobot\" ;\n" + - " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + - " td:hasPropertyAffordance [\n" + - " a td:PropertyAffordance, js:BooleanSchema, ex:Status ; \n" + - " td:hasForm [\n" + - " hctl:hasTarget ; \n" + - " ] ; \n" + - " ] ;\n" + - " td:hasActionAffordance [\n" + - " a td:ActionAffordance, ex:CarryFromTo ;\n" + - " dct:title \"carry\" ; \n" + - " td:hasForm [\n" + - " hctl:hasTarget ; \n" + - " ] ; \n" + - " td:hasInputSchema [ \n" + - " a js:ObjectSchema ;\n" + - " js:properties [ \n" + - " a js:ArraySchema, ex:SourcePosition ;\n" + - " js:propertyName \"sourcePosition\";\n" + - " js:minItems 3 ;\n" + - " js:maxItems 3 ;\n" + - " js:items [\n" + - " a js:NumberSchema ;\n" + - " ] ;\n" + - " ] ;\n" + - " js:properties [\n" + - " a js:ArraySchema, ex:TargetPosition ;\n" + - " js:propertyName \"targetPosition\";\n" + - " js:minItems 3 ;\n" + - " js:maxItems 3 ;\n" + - " js:items [\n" + - " a js:NumberSchema ;\n" + - " ] ;\n" + - " ] ;\n" + + + private static final String FORKLIFT_ROBOT_TD = "@prefix td: .\n" + + "@prefix htv: .\n" + + "@prefix hctl: .\n" + + "@prefix dct: .\n" + + "@prefix wotsec: .\n" + + "@prefix js: .\n" + + "@prefix ex: .\n" + + "\n" + + "ex:forkliftRobot a td:Thing ; \n" + + " dct:title \"forkliftRobot\" ;\n" + + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + + " td:hasPropertyAffordance [\n" + + " a td:PropertyAffordance, js:BooleanSchema, ex:Status ; \n" + + " td:hasForm [\n" + + " hctl:hasTarget ; \n" + + " ] ; \n" + + " ] ;\n" + + " td:hasActionAffordance [\n" + + " a td:ActionAffordance, ex:CarryFromTo ;\n" + + " dct:title \"carry\" ; \n" + + " td:hasForm [\n" + + " hctl:hasTarget ; \n" + + " ] ; \n" + + " td:hasInputSchema [ \n" + + " a js:ObjectSchema ;\n" + + " js:properties [ \n" + + " a js:ArraySchema, ex:SourcePosition ;\n" + + " js:propertyName \"sourcePosition\";\n" + + " js:minItems 3 ;\n" + + " js:maxItems 3 ;\n" + + " js:items [\n" + + " a js:NumberSchema ;\n" + + " ] ;\n" + + " ] ;\n" + + " js:properties [\n" + + " a js:ArraySchema, ex:TargetPosition ;\n" + + " js:propertyName \"targetPosition\";\n" + + " js:minItems 3 ;\n" + + " js:maxItems 3 ;\n" + + " js:items [\n" + + " a js:NumberSchema ;\n" + + " ] ;\n" + + " ] ;\n" + " js:required \"sourcePosition\", \"targetPosition\" ;" + - " ] ; \n" + + " ] ; \n" + " ] .\n"; - + private ThingDescription td; - + @Before public void init() { td = TDGraphReader.readFromString(TDFormat.RDF_TURTLE, FORKLIFT_ROBOT_TD); } - + @Test public void testToStringNullEntity() { TDHttpRequest request = new TDHttpRequest(new Form.Builder("http://example.org/action") - .addOperationType(TD.invokeAction).build(), + .addOperationType(TD.invokeAction).build(), TD.invokeAction); - + assertEquals("[TDHttpRequest] Method: POST, Target: http://example.org/action, " + "Content-Type: application/json", request.toString()); } - + @Test public void testWriteProperty() throws UnsupportedOperationException, IOException { assertEquals(1, td.getProperties().size()); @@ -134,143 +134,143 @@ public void testWriteProperty() throws UnsupportedOperationException, IOExceptio assertTrue(property.isPresent()); Optional form = property.get().getFirstFormForOperationType(TD.writeProperty); assertTrue(form.isPresent()); - + BasicClassicHttpRequest request = new TDHttpRequest(form.get(), TD.writeProperty) .setPrimitivePayload(property.get().getDataSchema(), true) .getRequest(); - + assertEquals("PUT", request.getMethod()); - + StringWriter writer = new StringWriter(); IOUtils.copy(request.getEntity().getContent(), writer, StandardCharsets.UTF_8.name()); JsonElement payload = JsonParser.parseString(writer.toString()); - + assertTrue(payload.isJsonPrimitive()); assertTrue(payload.getAsBoolean()); } - + @Test public void testInvokeAction() throws UnsupportedOperationException, IOException { Optional action = td.getFirstActionBySemanticType(PREFIX + "CarryFromTo"); assertTrue(action.isPresent()); Optional form = action.get().getFirstForm(); assertTrue(form.isPresent()); - + Map payloadVariables = new HashMap(); payloadVariables.put(PREFIX + "SourcePosition", Arrays.asList(30, 50, 70)); payloadVariables.put(PREFIX + "TargetPosition", Arrays.asList(30, 60, 70)); - + BasicClassicHttpRequest request = new TDHttpRequest(form.get(), TD.invokeAction) .setObjectPayload((ObjectSchema) action.get().getInputSchema().get(), payloadVariables) .getRequest(); - + assertEquals("POST", request.getMethod()); - + StringWriter writer = new StringWriter(); IOUtils.copy(request.getEntity().getContent(), writer, StandardCharsets.UTF_8.name()); JsonObject payload = JsonParser.parseString(writer.toString()).getAsJsonObject(); - + JsonArray sourcePosition = payload.get("sourcePosition").getAsJsonArray(); assertEquals(30, sourcePosition.get(0).getAsInt()); assertEquals(50, sourcePosition.get(1).getAsInt()); assertEquals(70, sourcePosition.get(2).getAsInt()); - + JsonArray targetPosition = payload.get("targetPosition").getAsJsonArray(); assertEquals(30, targetPosition.get(0).getAsInt()); assertEquals(60, targetPosition.get(1).getAsInt()); assertEquals(70, targetPosition.get(2).getAsInt()); } - + @Test public void testNoPayload() { BasicClassicHttpRequest request = new TDHttpRequest(FORM, TD.invokeAction) .getRequest(); assertNull(request.getEntity()); } - + @Test - public void testSimpleObjectPayload() throws ProtocolException, URISyntaxException, + public void testSimpleObjectPayload() throws ProtocolException, URISyntaxException, JsonSyntaxException, ParseException, IOException { ObjectSchema payloadSchema = new ObjectSchema.Builder() .addProperty("first_name", new StringSchema.Builder().build()) .addProperty("last_name", new StringSchema.Builder().build()) .build(); - + Map payloadVariables = new HashMap(); payloadVariables.put("first_name", "Andrei"); payloadVariables.put("last_name", "Ciortea"); - + BasicClassicHttpRequest request = new TDHttpRequest(FORM, TD.invokeAction) .setObjectPayload(payloadSchema, payloadVariables) .getRequest(); - + assertUserSchemaPayload(request); } - + @Test - public void testSimpleSemanticObjectPayload() throws ProtocolException, URISyntaxException, + public void testSimpleSemanticObjectPayload() throws ProtocolException, URISyntaxException, JsonSyntaxException, ParseException, IOException { Map payloadVariables = new HashMap(); payloadVariables.put(PREFIX + "FirstName", "Andrei"); payloadVariables.put(PREFIX + "LastName", "Ciortea"); - + BasicClassicHttpRequest request = new TDHttpRequest(FORM, TD.invokeAction) .setObjectPayload(USER_SCHEMA, payloadVariables) .getRequest(); - + assertEquals("PUT", request.getMethod()); assertEquals(0, request.getUri().compareTo(URI.create(PREFIX + "toggle"))); assertUserSchemaPayload(request); } - + @Test(expected = IllegalArgumentException.class) public void testInvalidBooleanPayload() { new TDHttpRequest(FORM, TD.invokeAction) .setPrimitivePayload(new BooleanSchema.Builder().build(), "string") .getRequest(); } - + @Test(expected = IllegalArgumentException.class) public void testInvalidIntegerPayload() { new TDHttpRequest(FORM, TD.invokeAction) .setPrimitivePayload(new IntegerSchema.Builder().build(), 0.5) .getRequest(); } - + @Test(expected = IllegalArgumentException.class) public void testInvalidStringPayload() { new TDHttpRequest(FORM, TD.invokeAction) .setPrimitivePayload(new StringSchema.Builder().build(), true) .getRequest(); } - + @Test public void testArrayPayload() throws UnsupportedOperationException, IOException { ArraySchema payloadSchema = new ArraySchema.Builder() .addItem(new NumberSchema.Builder().build()) .build(); - + List payloadVariables = new ArrayList(); payloadVariables.add(1); payloadVariables.add(3); payloadVariables.add(5); - + BasicClassicHttpRequest request = new TDHttpRequest(FORM, TD.invokeAction) .setArrayPayload(payloadSchema, payloadVariables) .getRequest(); - + StringWriter writer = new StringWriter(); IOUtils.copy(request.getEntity().getContent(), writer, StandardCharsets.UTF_8.name()); - + JsonArray payload = JsonParser.parseString(writer.toString()).getAsJsonArray(); assertEquals(3, payload.size()); assertEquals(1, payload.get(0).getAsInt()); assertEquals(3, payload.get(1).getAsInt()); assertEquals(5, payload.get(2).getAsInt()); } - + @Test - public void testSemanticObjectWithOneArrayPayload() throws UnsupportedOperationException, + public void testSemanticObjectWithOneArrayPayload() throws UnsupportedOperationException, IOException { ObjectSchema payloadSchema = new ObjectSchema.Builder() .addProperty("speed", new NumberSchema.Builder() @@ -281,55 +281,55 @@ public void testSemanticObjectWithOneArrayPayload() throws UnsupportedOperationE .addItem(new IntegerSchema.Builder().build()) .build()) .build(); - + List coordinates = new ArrayList(); coordinates.add(30); coordinates.add(50); coordinates.add(70); - + Map payloadVariables = new HashMap(); payloadVariables.put(PREFIX + "Speed", 3.5); payloadVariables.put(PREFIX + "3DCoordinates", coordinates); - + BasicClassicHttpRequest request = new TDHttpRequest(FORM, TD.invokeAction) .setObjectPayload(payloadSchema, payloadVariables) .getRequest(); - + StringWriter writer = new StringWriter(); IOUtils.copy(request.getEntity().getContent(), writer, StandardCharsets.UTF_8.name()); - + JsonObject payload = JsonParser.parseString(writer.toString()).getAsJsonObject(); assertEquals(3.5, payload.get("speed").getAsDouble(), 0.01); - + JsonArray coordinatesArray = payload.getAsJsonArray("coordinates"); assertEquals(3, coordinatesArray.size()); assertEquals(30, coordinatesArray.get(0).getAsInt()); assertEquals(50, coordinatesArray.get(1).getAsInt()); assertEquals(70, coordinatesArray.get(2).getAsInt()); } - + @Test public void testArrayOfSemanticObjectsPayload() { // TODO } - + @Test public void testSemanticObjectWithArrayOfSemanticObjectsPayload() { // TODO } - + @Test public void testValidateArrayPayload() { // TODO } - - private void assertUserSchemaPayload(BasicClassicHttpRequest request) + + private void assertUserSchemaPayload(BasicClassicHttpRequest request) throws UnsupportedOperationException, IOException, ProtocolException { StringWriter writer = new StringWriter(); IOUtils.copy(request.getEntity().getContent(), writer, StandardCharsets.UTF_8.name()); - + assertEquals("application/json", request.getHeader(HttpHeaders.CONTENT_TYPE).getValue()); - + JsonObject payload = JsonParser.parseString(writer.toString()).getAsJsonObject(); assertEquals("Andrei", payload.get("first_name").getAsString()); assertEquals("Ciortea", payload.get("last_name").getAsString()); diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphReaderTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphReaderTest.java similarity index 91% rename from src/test/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphReaderTest.java rename to src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphReaderTest.java index 72513fc0..b1ab7462 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphReaderTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphReaderTest.java @@ -1,4 +1,4 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -6,6 +6,8 @@ import java.io.IOException; import java.util.Optional; +import ch.unisg.ics.interactions.wot.td.io.graph.ReadWriteUtils; +import ch.unisg.ics.interactions.wot.td.io.graph.SchemaGraphReader; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.ValueFactory; @@ -28,91 +30,91 @@ public class SchemaGraphReaderTest { private static final ValueFactory rdf = SimpleValueFactory.getInstance(); private static final String SEMANTIC_USER_ACCOUNT = "[\n" + - " a js:ObjectSchema, ;\n" + - " js:properties [\n" + + " a js:ObjectSchema, ;\n" + + " js:properties [\n" + " a js:StringSchema, ;\n" + " js:propertyName \"full_name\";\n" + " ] ;\n" + " js:required \"full_name\" ;\n" + " ]"; - + @Test - public void testReadSimpleSemanticObject() throws RDFParseException, RDFHandlerException, + public void testReadSimpleSemanticObject() throws RDFParseException, RDFHandlerException, IOException { - + String testSimpleSemObject = "@prefix td: .\n" + "@prefix js: .\n" + - "[\n" + - " a js:ObjectSchema, ;\n" + - " js:properties [\n" + + "[\n" + + " a js:ObjectSchema, ;\n" + + " js:properties [\n" + " a js:BooleanSchema, ;\n" + " js:propertyName \"boolean_value\";\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:NumberSchema, ;\n" + " js:propertyName \"number_value\";\n" + - " js:maximum 100.05 ;\n" + + " js:maximum 100.05 ;\n" + " js:minimum -100.05 ;\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:IntegerSchema, ;\n" + " js:propertyName \"integer_value\";\n" + - " js:maximum 100 ;\n" + + " js:maximum 100 ;\n" + " js:minimum -100 ;\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:StringSchema, ;\n" + " js:propertyName \"string_value\";\n" + " js:enum \"label1\", , \"label3\" ;\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:NullSchema, ;\n" + " js:propertyName \"null_value\";\n" + " ] ;\n" + " js:required \"integer_value\", \"number_value\" ;\n" + "] .\n"; - + ObjectSchema object = assertObjectMetadata(testSimpleSemObject, PREFIX + "SemObject", 5, 2); - + DataSchema booleanProperty = object.getProperties().get("boolean_value"); assertEquals(DataSchema.BOOLEAN, booleanProperty.getDatatype()); assertTrue(booleanProperty.getSemanticTypes().contains(PREFIX + "SemBool")); - + DataSchema integerProperty = object.getProperties().get("integer_value"); assertEquals(DataSchema.INTEGER, integerProperty.getDatatype()); assertTrue(integerProperty.getSemanticTypes().contains(PREFIX + "SemInt")); - + DataSchema numberProperty = object.getProperties().get("number_value"); assertEquals(DataSchema.NUMBER, numberProperty.getDatatype()); assertTrue(numberProperty.getSemanticTypes().contains(PREFIX + "SemNumber")); - + DataSchema stringProperty = object.getProperties().get("string_value"); assertEquals(DataSchema.STRING, stringProperty.getDatatype()); assertTrue(stringProperty.getSemanticTypes().contains(PREFIX + "SemString")); assertEquals(3,stringProperty.getEnumeration().size()); assertTrue(stringProperty.getEnumeration().contains("label1")); assertTrue(stringProperty.getEnumeration().contains("http://example.org/label2")); - + DataSchema nullProperty = object.getProperties().get("null_value"); assertEquals(DataSchema.NULL, nullProperty.getDatatype()); assertTrue(nullProperty.getSemanticTypes().contains(PREFIX + "SemNull")); } - + @Test - public void testReadSimpleSemanticObjectWithArray() throws RDFParseException, + public void testReadSimpleSemanticObjectWithArray() throws RDFParseException, RDFHandlerException, IOException { - + String testObject = "@prefix td: .\n" + "@prefix js: .\n" + - "[\n" + - " a js:ObjectSchema, ;\n" + - " js:properties [\n" + + "[\n" + + " a js:ObjectSchema, ;\n" + + " js:properties [\n" + " a js:IntegerSchema, ;\n" + " js:propertyName \"count\";\n" + " ] ,\n" + - " [\n" + + " [\n" + " a js:ArraySchema, ;\n" + " js:propertyName \"user_list\";\n" + " js:minItems 0 ;\n" + @@ -121,19 +123,19 @@ public void testReadSimpleSemanticObjectWithArray() throws RDFParseException, " ] ;\n" + " js:required \"count\" ;\n" + "] .\n"; - + ObjectSchema object = assertObjectMetadata(testObject, PREFIX + "UserDB", 2, 1); - + DataSchema count = object.getProperties().get("count"); assertEquals(DataSchema.INTEGER, count.getDatatype()); assertTrue(count.getSemanticTypes().contains(PREFIX + "UserCount")); - + ArraySchema array = (ArraySchema) object.getProperties().get("user_list"); assertEquals(DataSchema.ARRAY, array.getDatatype()); assertEquals(100, array.getMaxItems().get().intValue()); assertEquals(0, array.getMinItems().get().intValue()); assertEquals(1, array.getItems().size()); - + ObjectSchema user = (ObjectSchema) array.getItems().get(0); assertEquals(DataSchema.OBJECT, user.getDatatype()); assertEquals(1, user.getProperties().size()); @@ -143,40 +145,40 @@ public void testReadSimpleSemanticObjectWithArray() throws RDFParseException, assertTrue(user.getProperties().get("full_name").getSemanticTypes() .contains(PREFIX + "FullName")); } - + @Test - public void testReadNestedSemanticObject() throws RDFParseException, RDFHandlerException, + public void testReadNestedSemanticObject() throws RDFParseException, RDFHandlerException, IOException { - + String testNestedSemanticObject = "@prefix td: .\n" + "@prefix js: .\n" + - "[\n" + + "[\n" + " a js:ObjectSchema, ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:StringSchema, ;\n" + " js:propertyName \"string_value\";\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:ObjectSchema, ;\n" + " js:propertyName \"inner_object\";\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:BooleanSchema, ;\n" + " js:propertyName \"boolean_value\";\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:NumberSchema, ;\n" + " js:propertyName \"number_value\";\n" + - " js:maximum 100.05 ;\n" + + " js:maximum 100.05 ;\n" + " js:minimum -100.05 ;\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:IntegerSchema, ;\n" + " js:propertyName \"integer_value\";\n" + - " js:maximum 100 ;\n" + + " js:maximum 100 ;\n" + " js:minimum -100 ;\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:NullSchema, ;\n" + " js:propertyName \"null_value\";\n" + " ] ;\n" + @@ -184,65 +186,65 @@ public void testReadNestedSemanticObject() throws RDFParseException, RDFHandlerE " ] ;\n" + " js:required \"string_value\" ;\n" + "] .\n"; - - Model model = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, testNestedSemanticObject, + + Model model = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, testNestedSemanticObject, IO_BASE_IRI); - Optional nodeId = Models.subject(model.filter(null, RDF.TYPE, + Optional nodeId = Models.subject(model.filter(null, RDF.TYPE, SimpleValueFactory.getInstance().createIRI(PREFIX + "SemObject"))); - + Optional schema = SchemaGraphReader.readDataSchema(nodeId.get(), model); assertTrue(schema.isPresent()); assertEquals(DataSchema.OBJECT, schema.get().getDatatype()); assertTrue(schema.get().getSemanticTypes().contains(PREFIX + "SemObject")); - + ObjectSchema object = (ObjectSchema) schema.get(); assertEquals(2, object.getProperties().size()); assertTrue(object.getRequiredProperties().contains("string_value")); - + DataSchema stringProperty = object.getProperties().get("string_value"); assertEquals(DataSchema.STRING, stringProperty.getDatatype()); assertTrue(stringProperty.getSemanticTypes().contains(PREFIX + "SemString")); - + ObjectSchema innerObject = (ObjectSchema) object.getProperties().get("inner_object"); assertTrue(innerObject.getSemanticTypes().contains(PREFIX + "AnotherSemObject")); assertEquals(4, innerObject.getProperties().size()); assertTrue(innerObject.getRequiredProperties().contains("integer_value")); - + DataSchema booleanProperty = innerObject.getProperties().get("boolean_value"); assertEquals(DataSchema.BOOLEAN, booleanProperty.getDatatype()); assertTrue(booleanProperty.getSemanticTypes().contains(PREFIX + "SemBool")); - + DataSchema integerProperty = innerObject.getProperties().get("integer_value"); assertEquals(DataSchema.INTEGER, integerProperty.getDatatype()); assertTrue(integerProperty.getSemanticTypes().contains(PREFIX + "SemInt")); - + DataSchema numberProperty = innerObject.getProperties().get("number_value"); assertEquals(DataSchema.NUMBER, numberProperty.getDatatype()); assertTrue(numberProperty.getSemanticTypes().contains(PREFIX + "SemNumber")); - + DataSchema nullProperty = innerObject.getProperties().get("null_value"); assertEquals(DataSchema.NULL, nullProperty.getDatatype()); assertTrue(nullProperty.getSemanticTypes().contains(PREFIX + "SemNull")); } - + @Test public void testReadArrayOneSemanticObject() throws RDFParseException, RDFHandlerException, IOException { - + String testArray = "@prefix td: .\n" + "@prefix js: .\n" + - "[\n" + + "[\n" + " a js:ArraySchema, ;\n" + " js:minItems 0 ;\n" + " js:maxItems 100 ;\n" + " js:items " + SEMANTIC_USER_ACCOUNT + ";\n" + "] .\n"; - + ArraySchema array = assertUserAccountArrayMetadata(testArray); assertEquals(1, array.getItems().size()); assertEquals(DataSchema.OBJECT, array.getItems().get(0).getDatatype()); - + ObjectSchema user = (ObjectSchema) array.getItems().get(0); assertTrue(user.getSemanticTypes().contains(PREFIX + "UserAccount")); assertEquals(1, user.getProperties().size()); @@ -252,64 +254,64 @@ public void testReadArrayOneSemanticObject() throws RDFParseException, RDFHandle assertEquals(1, user.getRequiredProperties().size()); assertTrue(user.getRequiredProperties().contains("full_name")); } - + @Test public void testReadArrayMultipleSemanticObjects() throws RDFParseException, RDFHandlerException, IOException { - + String testArray = "@prefix td: .\n" + "@prefix js: .\n" + - "[\n" + + "[\n" + " a js:ArraySchema, ;\n" + " js:minItems 0 ;\n" + " js:maxItems 100 ;\n" + " js:items " + SEMANTIC_USER_ACCOUNT + ";\n" + " js:items " + SEMANTIC_USER_ACCOUNT + ";\n" + "] .\n"; - + ArraySchema array = assertUserAccountArrayMetadata(testArray); - + assertEquals(2, array.getItems().size()); assertEquals(DataSchema.OBJECT, array.getItems().get(0).getDatatype()); assertEquals(DataSchema.OBJECT, array.getItems().get(1).getDatatype()); } - - - private ObjectSchema assertObjectMetadata(String testSemObject, String semType, int props, int req) + + + private ObjectSchema assertObjectMetadata(String testSemObject, String semType, int props, int req) throws RDFParseException, RDFHandlerException, IOException { - Model model = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, testSemObject, + Model model = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, testSemObject, IO_BASE_IRI); - Optional nodeId = Models.subject(model.filter(null, RDF.TYPE, + Optional nodeId = Models.subject(model.filter(null, RDF.TYPE, rdf.createIRI(JSONSchema.ObjectSchema))); - + Optional schema = SchemaGraphReader.readDataSchema(nodeId.get(), model); - + assertTrue(schema.isPresent()); assertEquals(DataSchema.OBJECT, schema.get().getDatatype()); assertTrue(schema.get().getSemanticTypes().contains(semType)); - + ObjectSchema object = (ObjectSchema) schema.get(); assertEquals(props, object.getProperties().size()); assertEquals(req, object.getRequiredProperties().size()); - + return (ObjectSchema) schema.get(); } - + private ArraySchema assertUserAccountArrayMetadata(String testArray) throws RDFParseException, RDFHandlerException, IOException { Model model = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, testArray, IO_BASE_IRI); - Optional nodeId = Models.subject(model.filter(null, RDF.TYPE, + Optional nodeId = Models.subject(model.filter(null, RDF.TYPE, rdf.createIRI(JSONSchema.ArraySchema))); - + DataSchema schema = SchemaGraphReader.readDataSchema(nodeId.get(), model).get(); assertEquals(DataSchema.ARRAY, schema.getDatatype()); - + ArraySchema array = (ArraySchema) schema; assertTrue(array.getSemanticTypes().contains(PREFIX + "UserAccountList")); assertEquals(0, array.getMinItems().get().intValue()); assertEquals(100, array.getMaxItems().get().intValue()); - + return array; } } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphWriterTest.java similarity index 82% rename from src/test/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphWriterTest.java rename to src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphWriterTest.java index b2d40c50..7263c539 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/SchemaGraphWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/SchemaGraphWriterTest.java @@ -1,4 +1,4 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -8,6 +8,8 @@ import java.util.Optional; import java.util.Set; +import ch.unisg.ics.interactions.wot.td.io.graph.ReadWriteUtils; +import ch.unisg.ics.interactions.wot.td.io.graph.SchemaGraphWriter; import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Model; @@ -34,51 +36,51 @@ public class SchemaGraphWriterTest { private final static String IO_BASE_IRI = "http://example.org/"; private final static String PREFIX = "https://example.org/#"; - - private final static String TEST_SCHEMA_PREFIXES = + + private final static String TEST_SCHEMA_PREFIXES = "@prefix js: .\n" + "@prefix xsd: .\n" + "@prefix ex: .\n"; - - private final static String SEMANTIC_OBJECT = "[\n" + - " a js:ObjectSchema, ex:SemObject ;\n" + - " js:properties [\n" + - " a js:BooleanSchema, ex:SemBoolean ;\n" + + + private final static String SEMANTIC_OBJECT = "[\n" + + " a js:ObjectSchema, ex:SemObject ;\n" + + " js:properties [\n" + + " a js:BooleanSchema, ex:SemBoolean ;\n" + " js:propertyName \"boolean_value\";\n" + " ] ;\n" + " js:properties [\n" + - " a js:IntegerSchema, ex:SemInteger ;\n" + + " a js:IntegerSchema, ex:SemInteger ;\n" + " js:propertyName \"integer_value\" ;\n" + " js:minimum \"-1000\"^^xsd:int ;\n" + " js:maximum \"1000\"^^xsd:int ;\n" + " ] ;\n" + - " js:properties [\n" + - " a js:NumberSchema, ex:SemNumber ;\n" + + " js:properties [\n" + + " a js:NumberSchema, ex:SemNumber ;\n" + " js:propertyName \"number_value\";\n" + " ] ;\n" + - " js:properties [\n" + - " a js:StringSchema, ex:SemString ;\n" + + " js:properties [\n" + + " a js:StringSchema, ex:SemString ;\n" + " js:propertyName \"string_value\";\n" + " ] ;\n" + - " js:properties [\n" + - " a js:NullSchema, ex:SemNull ;\n" + + " js:properties [\n" + + " a js:NullSchema, ex:SemNull ;\n" + " js:propertyName \"null_value\";\n" + " ] ;\n" + " js:required \"string_value\" ;\n" + "]"; - - private final static String USER_ACCOUNT_OBJECT = "[\n" + + + private final static String USER_ACCOUNT_OBJECT = "[\n" + " a js:ObjectSchema, ex:UserAccount ;\n" + - " js:properties [\n" + - " a js:StringSchema, ex:FullName ;\n" + + " js:properties [\n" + + " a js:StringSchema, ex:FullName ;\n" + " js:propertyName \"full_name\";\n" + " ] ;\n" + " js:required \"full_name\" ;\n" + " ]"; - + private static DataSchema semanticObjectSchema; - private static final ValueFactory rdf = SimpleValueFactory.getInstance(); - + private static final ValueFactory rdf = SimpleValueFactory.getInstance(); + @Before public void init() { semanticObjectSchema = new ObjectSchema.Builder() @@ -103,35 +105,35 @@ public void init() { .addRequiredProperties("string_value") .build(); } - + @Test public void testWriteEnumeration() throws RDFParseException, RDFHandlerException, IOException { - String expectedSchema = TEST_SCHEMA_PREFIXES + "[ a js:StringSchema ;\n" + + String expectedSchema = TEST_SCHEMA_PREFIXES + "[ a js:StringSchema ;\n" + " js:enum \"label1\", , \"label3\";\n" + " ] .\n"; - + Set enumeration = new HashSet(); enumeration.add("label1"); enumeration.add("http://example.org/label2"); enumeration.add("label3"); - + DataSchema schema = new StringSchema.Builder() .addEnum(enumeration) .build(); - + assertModel(expectedSchema, schema); } - + // Serialization of decimal values requires specific testing (not considered in this test) @Test - public void testWriteSemanticObjectNoDecimals() throws RDFParseException, + public void testWriteSemanticObjectNoDecimals() throws RDFParseException, RDFHandlerException, IOException { - String expectedSchema = TEST_SCHEMA_PREFIXES + SEMANTIC_OBJECT + " .\n"; + String expectedSchema = TEST_SCHEMA_PREFIXES + SEMANTIC_OBJECT + " .\n"; assertModel(expectedSchema, semanticObjectSchema); } - + @Test - public void testWriteSemanticObjectWithDecimals() throws RDFParseException, + public void testWriteSemanticObjectWithDecimals() throws RDFParseException, RDFHandlerException, IOException { DataSchema schema = new ObjectSchema.Builder() .addSemanticType(PREFIX + "SemObject") @@ -141,44 +143,44 @@ public void testWriteSemanticObjectWithDecimals() throws RDFParseException, .addMaximum(1000.005) .build()) .build(); - + String description = getTestModelDescription(schema); - Model schemaModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, description, + Model schemaModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, description, IO_BASE_IRI); - - Optional minimum = Models.objectLiteral(schemaModel.filter(null, + + Optional minimum = Models.objectLiteral(schemaModel.filter(null, rdf.createIRI(JSONSchema.minimum), null)); assertTrue(minimum.isPresent()); assertEquals(-1000.005, minimum.get().doubleValue(), 0.001); - - Optional maximum = Models.objectLiteral(schemaModel.filter(null, + + Optional maximum = Models.objectLiteral(schemaModel.filter(null, rdf.createIRI(JSONSchema.minimum), null)); assertTrue(maximum.isPresent()); assertEquals(-1000.005, maximum.get().doubleValue(), 0.001); } - + @Test - public void testWriteNestedSemanticObject() throws RDFParseException, RDFHandlerException, + public void testWriteNestedSemanticObject() throws RDFParseException, RDFHandlerException, IOException { String expectedSchema = TEST_SCHEMA_PREFIXES + - "[\n" + + "[\n" + " a js:ObjectSchema, ex:SemObject ;\n" + - " js:properties [\n" + - " a js:StringSchema, ex:SemString ;\n" + + " js:properties [\n" + + " a js:StringSchema, ex:SemString ;\n" + " js:propertyName \"string_value\";\n" + " ] ;\n" + - " js:properties [\n" + - " a js:ObjectSchema, ex:AnotherSemObject ;\n" + + " js:properties [\n" + + " a js:ObjectSchema, ex:AnotherSemObject ;\n" + " js:propertyName \"inner_object\";\n" + " js:properties [\n" + - " a js:IntegerSchema, ex:SemInteger ;\n" + + " a js:IntegerSchema, ex:SemInteger ;\n" + " js:propertyName \"integer_value\" ;\n" + " ] ;\n" + " js:required \"integer_value\" ;\n" + " ] ;\n" + " js:required \"string_value\" ;\n" + "] ." ; - + DataSchema schema = new ObjectSchema.Builder() .addSemanticType(PREFIX + "SemObject") .addProperty("string_value", new StringSchema.Builder() @@ -193,30 +195,30 @@ public void testWriteNestedSemanticObject() throws RDFParseException, RDFHandler .build()) .addRequiredProperties("string_value") .build(); - + assertModel(expectedSchema, schema); } - + @Test - public void testWriteObjectWithArray() throws RDFParseException, RDFHandlerException, + public void testWriteObjectWithArray() throws RDFParseException, RDFHandlerException, IOException { String expectedSchema = TEST_SCHEMA_PREFIXES + - "[\n" + + "[\n" + " a js:ObjectSchema, ex:UserDB ;\n" + - " js:properties [\n" + - " a js:IntegerSchema, ex:UserCount ;\n" + + " js:properties [\n" + + " a js:IntegerSchema, ex:UserCount ;\n" + " js:propertyName \"count\";\n" + " ] ;\n" + - " js:properties [\n" + - " a js:ArraySchema, ex:UserAccountList ;\n" + + " js:properties [\n" + + " a js:ArraySchema, ex:UserAccountList ;\n" + " js:propertyName \"user_list\";\n" + " js:minItems \"0\"^^xsd:int ;\n" + " js:maxItems \"100\"^^xsd:int ;\n" + - " js:items " + USER_ACCOUNT_OBJECT + ";\n" + + " js:items " + USER_ACCOUNT_OBJECT + ";\n" + " ] ;\n" + " js:required \"count\" ;\n" + "] .\n"; - + ObjectSchema schema = new ObjectSchema.Builder() .addSemanticType(PREFIX + "UserDB") .addProperty("count", new IntegerSchema.Builder() @@ -236,21 +238,21 @@ public void testWriteObjectWithArray() throws RDFParseException, RDFHandlerExcep .build()) .addRequiredProperties("count") .build(); - + assertModel(expectedSchema, schema); } - + @Test - public void testWriteArrayOneObject() throws RDFParseException, RDFHandlerException, + public void testWriteArrayOneObject() throws RDFParseException, RDFHandlerException, IOException { String expectedSchema = TEST_SCHEMA_PREFIXES + - "[\n" + - " a js:ArraySchema, ex:UserAccountList ;\n" + + "[\n" + + " a js:ArraySchema, ex:UserAccountList ;\n" + " js:minItems \"0\"^^xsd:int ;\n" + " js:maxItems \"100\"^^xsd:int ;\n" + - " js:items " + USER_ACCOUNT_OBJECT + ";\n" + + " js:items " + USER_ACCOUNT_OBJECT + ";\n" + "] ." ; - + ObjectSchema userAccount = getUserAccountSchema(); ArraySchema schema = new ArraySchema.Builder() .addSemanticType(PREFIX + "UserAccountList") @@ -258,31 +260,31 @@ public void testWriteArrayOneObject() throws RDFParseException, RDFHandlerExcept .addMinItems(0) .addItem(userAccount) .build(); - + assertModel(expectedSchema, schema); } - + @Test - public void testWriteArrayMultipleObjects() throws RDFParseException, RDFHandlerException, + public void testWriteArrayMultipleObjects() throws RDFParseException, RDFHandlerException, IOException { String expectedSchema = TEST_SCHEMA_PREFIXES + - "[\n" + - " a js:ArraySchema, ex:UserAccountList ;\n" + - " js:items " + USER_ACCOUNT_OBJECT + ";\n" + - " js:items " + USER_ACCOUNT_OBJECT + ";\n" + + "[\n" + + " a js:ArraySchema, ex:UserAccountList ;\n" + + " js:items " + USER_ACCOUNT_OBJECT + ";\n" + + " js:items " + USER_ACCOUNT_OBJECT + ";\n" + "] ." ; - + ObjectSchema userAccount = getUserAccountSchema(); - + ArraySchema schema = new ArraySchema.Builder() .addSemanticType(PREFIX + "UserAccountList") .addItem(userAccount) .addItem(userAccount) .build(); - + assertModel(expectedSchema, schema); } - + private ObjectSchema getUserAccountSchema() { return new ObjectSchema.Builder() .addSemanticType(PREFIX + "UserAccount") @@ -292,24 +294,24 @@ private ObjectSchema getUserAccountSchema() { .addRequiredProperties("full_name") .build(); } - + private String getTestModelDescription(DataSchema testSchema) { ModelBuilder builder = new ModelBuilder(); BNode nodeId = SimpleValueFactory.getInstance().createBNode(); SchemaGraphWriter.write(builder, nodeId, testSchema); - + return ReadWriteUtils.writeToString(RDFFormat.TURTLE, builder.build()); } - - private void assertModel(String expectedSchema, DataSchema schema) throws RDFParseException, + + private void assertModel(String expectedSchema, DataSchema schema) throws RDFParseException, RDFHandlerException, IOException { - Model expectedModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, expectedSchema, + Model expectedModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, expectedSchema, IO_BASE_IRI); - + String description = getTestModelDescription(schema); - Model schemaModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, description, + Model schemaModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, description, IO_BASE_IRI); - + assertTrue(Models.isomorphic(expectedModel, schemaModel)); } } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/TDGraphReaderTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReaderTest.java similarity index 76% rename from src/test/java/ch/unisg/ics/interactions/wot/td/io/TDGraphReaderTest.java rename to src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReaderTest.java index a0def800..0878d790 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/TDGraphReaderTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReaderTest.java @@ -1,4 +1,4 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -11,6 +11,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import ch.unisg.ics.interactions.wot.td.io.InvalidTDException; +import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphReader; import org.eclipse.rdf4j.rio.RDFFormat; import org.junit.Test; @@ -30,7 +32,7 @@ import ch.unisg.ics.interactions.wot.td.vocabularies.WoTSec; public class TDGraphReaderTest { - + private static final String TEST_SIMPLE_TD = "@prefix td: .\n" + "@prefix htv: .\n" + @@ -39,146 +41,146 @@ public class TDGraphReaderTest { "@prefix wotsec: .\n" + "@prefix js: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + " td:hasBase ;\n" + - " td:hasPropertyAffordance [\n" + + " td:hasPropertyAffordance [\n" + " a td:PropertyAffordance, js:NumberSchema ;\n" + " td:name \"my_property\" ;\n" + " dct:title \"My Property\" ;\n" + " td:isObservable true ;\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + " hctl:hasOperationType td:writeProperty;\n" + - " ] ;\n" + - " td:hasForm [\n" + - " htv:methodName \"GET\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + + " ] ;\n" + + " td:hasForm [\n" + + " htv:methodName \"GET\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + " hctl:hasOperationType td:readProperty;\n" + " hctl:forSubProtocol \"websub\";\n" + - " ] ;\n" + - " ] ;\n" + - " td:hasActionAffordance [\n" + - " a td:ActionAffordance ;\n" + + " ] ;\n" + + " ] ;\n" + + " td:hasActionAffordance [\n" + + " a td:ActionAffordance ;\n" + " td:name \"my_action\" ;\n" + - " dct:title \"My Action\" ;\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasOperationType td:invokeAction;\n" + - " ] ;\n" + - " td:hasInputSchema [\n" + - " a js:ObjectSchema ;\n" + - " js:properties [\n" + + " dct:title \"My Action\" ;\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasOperationType td:invokeAction;\n" + + " ] ;\n" + + " td:hasInputSchema [\n" + + " a js:ObjectSchema ;\n" + + " js:properties [\n" + " a js:NumberSchema ;\n" + " js:propertyName \"number_value\";\n" + - " js:maximum 100.05 ;\n" + + " js:maximum 100.05 ;\n" + " js:minimum -100.05 ;\n" + " ] ;\n" + " js:required \"number_value\" ;\n" + - " ] ;\n" + - " td:hasOutputSchema [\n" + - " a js:ObjectSchema ;\n" + - " js:properties [\n" + + " ] ;\n" + + " td:hasOutputSchema [\n" + + " a js:ObjectSchema ;\n" + + " js:properties [\n" + " a js:BooleanSchema ;\n" + " js:propertyName \"boolean_value\";\n" + " ] ;\n" + " js:required \"boolean_value\" ;\n" + - " ]\n" + + " ]\n" + " ] ." ; - - private static final String TEST_SIMPLE_TD_JSONLD = "[ {\n" + - " \"@id\" : \"_:node1ea75dfphx111\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/security#NoSecurityScheme\" ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx112\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/td#ActionAffordance\" ],\n" + - " \"http://purl.org/dc/terms/title\" : [ {\n" + - " \"@value\" : \"My Action\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasForm\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx113\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasInputSchema\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx114\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasOutputSchema\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx116\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx113\",\n" + - " \"http://www.w3.org/2011/http#methodName\" : [ {\n" + - " \"@value\" : \"PUT\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/hypermedia#forContentType\" : [ {\n" + - " \"@value\" : \"application/json\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/hypermedia#hasOperationType\" : [ {\n" + - " \"@id\" : \"https://www.w3.org/2019/wot/td#invokeAction\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/hypermedia#hasTarget\" : [ {\n" + - " \"@id\" : \"http://example.org/action\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx114\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx115\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + - " \"@value\" : \"number_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx115\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#NumberSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#maximum\" : [ {\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + - " \"@value\" : \"100.05\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#minimum\" : [ {\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + - " \"@value\" : \"-100.05\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + - " \"@value\" : \"number_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx116\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx117\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + - " \"@value\" : \"boolean_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx117\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#BooleanSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + - " \"@value\" : \"boolean_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"http://example.org/#thing\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/td#Thing\" ],\n" + - " \"http://purl.org/dc/terms/title\" : [ {\n" + - " \"@value\" : \"My Thing\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasActionAffordance\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx112\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasBase\" : [ {\n" + - " \"@id\" : \"http://example.org/\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasSecurityConfiguration\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx111\"\n" + - " } ]\n" + + + private static final String TEST_SIMPLE_TD_JSONLD = "[ {\n" + + " \"@id\" : \"_:node1ea75dfphx111\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/security#NoSecurityScheme\" ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx112\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/td#ActionAffordance\" ],\n" + + " \"http://purl.org/dc/terms/title\" : [ {\n" + + " \"@value\" : \"My Action\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasForm\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx113\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasInputSchema\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx114\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasOutputSchema\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx116\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx113\",\n" + + " \"http://www.w3.org/2011/http#methodName\" : [ {\n" + + " \"@value\" : \"PUT\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/hypermedia#forContentType\" : [ {\n" + + " \"@value\" : \"application/json\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/hypermedia#hasOperationType\" : [ {\n" + + " \"@id\" : \"https://www.w3.org/2019/wot/td#invokeAction\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/hypermedia#hasTarget\" : [ {\n" + + " \"@id\" : \"http://example.org/action\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx114\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx115\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + + " \"@value\" : \"number_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx115\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#NumberSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#maximum\" : [ {\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + + " \"@value\" : \"100.05\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#minimum\" : [ {\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + + " \"@value\" : \"-100.05\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + + " \"@value\" : \"number_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx116\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx117\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + + " \"@value\" : \"boolean_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx117\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#BooleanSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + + " \"@value\" : \"boolean_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"http://example.org/#thing\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/td#Thing\" ],\n" + + " \"http://purl.org/dc/terms/title\" : [ {\n" + + " \"@value\" : \"My Thing\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasActionAffordance\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx112\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasBase\" : [ {\n" + + " \"@id\" : \"http://example.org/\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasSecurityConfiguration\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx111\"\n" + + " } ]\n" + "} ]"; - + private static final String TEST_IO_HEAD = "@prefix td: .\n" + "@prefix htv: .\n" + @@ -187,54 +189,54 @@ public class TDGraphReaderTest { "@prefix wotsec: .\n" + "@prefix js: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + - " td:hasBase ;\n" + - " td:hasActionAffordance [\n" + - " a td:ActionAffordance ;\n" + - " dct:title \"My Action\" ;\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasOperationType td:invokeAction;\n" + + " td:hasBase ;\n" + + " td:hasActionAffordance [\n" + + " a td:ActionAffordance ;\n" + + " dct:title \"My Action\" ;\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasOperationType td:invokeAction;\n" + " ] ;\n"; - + private static final String TEST_IO_TAIL = " ] ." ; - + @Test public void testReadTitle() { TDGraphReader reader = new TDGraphReader(RDFFormat.JSONLD, TEST_SIMPLE_TD_JSONLD); - + assertEquals("My Thing", reader.readThingTitle()); } - + @Test public void testReadThingTypes() { TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_SIMPLE_TD); - + assertEquals(1, reader.readThingTypes().size()); assertTrue(reader.readThingTypes().contains(TD.Thing)); } - + @Test public void testReadBaseURI() { TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_SIMPLE_TD); - + assertEquals("http://example.org/", reader.readBaseURI().get()); } - + @Test public void testReadOneSecurityScheme() { TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_SIMPLE_TD); - + assertEquals(1, reader.readSecuritySchemes().size()); - - assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> + + assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType().equals(WoTSec.NoSecurityScheme))); } - + @Test public void testReadAPIKeySecurityScheme() { String testTD = @@ -242,24 +244,24 @@ public void testReadAPIKeySecurityScheme() { "@prefix wotsec: .\n" + "@prefix dct: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:APIKeySecurityScheme ;\n" + " wotsec:in \"header\" ;\n" + " wotsec:name \"X-API-Key\" ;\n" + " ] ."; - + TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, testTD); - + assertEquals(1, reader.readSecuritySchemes().size()); - + SecurityScheme scheme = reader.readSecuritySchemes().iterator().next(); assertTrue(scheme instanceof APIKeySecurityScheme); assertEquals(WoTSec.APIKeySecurityScheme, ((APIKeySecurityScheme) scheme).getSchemeType()); assertEquals(TokenLocation.HEADER, ((APIKeySecurityScheme) scheme).getIn()); assertEquals("X-API-Key", ((APIKeySecurityScheme) scheme).getName().get()); } - + @Test public void testAPIKeySecuritySchemeDefaultValues() { String testTD = @@ -267,10 +269,10 @@ public void testAPIKeySecuritySchemeDefaultValues() { "@prefix wotsec: .\n" + "@prefix dct: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:APIKeySecurityScheme ] ."; - + TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, testTD); assertEquals(1, reader.readSecuritySchemes().size()); SecurityScheme scheme = reader.readSecuritySchemes().iterator().next(); @@ -278,7 +280,7 @@ public void testAPIKeySecuritySchemeDefaultValues() { assertEquals(TokenLocation.QUERY, ((APIKeySecurityScheme) scheme).getIn()); assertFalse(((APIKeySecurityScheme) scheme).getName().isPresent()); } - + @Test(expected = InvalidTDException.class) public void testAPIKeySecuritySchemeInvalidTokenLocation() { String testTD = @@ -286,15 +288,15 @@ public void testAPIKeySecuritySchemeInvalidTokenLocation() { "@prefix wotsec: .\n" + "@prefix dct: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:APIKeySecurityScheme ;\n" + " wotsec:in \"bla\" ;\n" + " ] ."; - + new TDGraphReader(RDFFormat.TURTLE, testTD).readSecuritySchemes(); } - + @Test public void testReadMultipleSecuritySchemes() { String testTD = @@ -304,57 +306,57 @@ public void testReadMultipleSecuritySchemes() { "@prefix dct: .\n" + "@prefix js: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + " td:hasSecurityConfiguration [ a wotsec:APIKeySecurityScheme ] ;\n" + " td:hasBase ."; - + TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, testTD); - + assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType() .equals(WoTSec.NoSecurityScheme))); assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType() .equals(WoTSec.APIKeySecurityScheme))); } - + @Test public void testReadOneSimpleProperty() { TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_SIMPLE_TD); - + List properties = reader.readProperties(); assertEquals(1, properties.size()); - + PropertyAffordance property = properties.get(0); assertEquals("my_property", property.getName().get()); assertEquals("My Property", property.getTitle().get()); assertTrue(property.isObservable()); assertEquals(2, property.getSemanticTypes().size()); assertEquals(2, property.getForms().size()); - + Optional form = property.getFirstFormForOperationType(TD.readProperty); assertTrue(form.isPresent()); assertEquals("websub", form.get().getSubProtocol().get()); } - + @Test public void testReadOneSimpleAction() { TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_SIMPLE_TD); - + assertEquals(1, reader.readActions().size()); ActionAffordance action = reader.readActions().get(0); - + assertEquals("my_action", action.getName().get()); assertEquals("My Action", action.getTitle().get()); assertEquals(1, action.getSemanticTypes().size()); assertEquals(TD.ActionAffordance, action.getSemanticTypes().get(0)); - + assertEquals(1, action.getForms().size()); Form form = action.getForms().get(0); - + assertForm(form, "PUT", "http://example.org/action", "application/json", TD.invokeAction); } - + @Test public void testReadMultipleSimpleActions() { String testTD = @@ -365,136 +367,136 @@ public void testReadMultipleSimpleActions() { "@prefix wotsec: .\n" + "@prefix js: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + - " td:hasBase ;\n" + - " td:hasActionAffordance [\n" + - " a td:ActionAffordance ;\n" + - " dct:title \"First Action\" ;\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasOperationType td:invokeAction;\n" + - " ] ;\n" + + " td:hasBase ;\n" + + " td:hasActionAffordance [\n" + + " a td:ActionAffordance ;\n" + + " dct:title \"First Action\" ;\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasOperationType td:invokeAction;\n" + + " ] ;\n" + " ] ;\n" + - " td:hasActionAffordance [\n" + - " a td:ActionAffordance ;\n" + - " dct:title \"Second Action\" ;\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasOperationType td:invokeAction;\n" + + " td:hasActionAffordance [\n" + + " a td:ActionAffordance ;\n" + + " dct:title \"Second Action\" ;\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasOperationType td:invokeAction;\n" + " ] ;\n" + " ] ;\n" + - " td:hasActionAffordance [\n" + - " a td:ActionAffordance ;\n" + - " dct:title \"Third Action\" ;\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasOperationType td:invokeAction;\n" + - " ] ;\n" + + " td:hasActionAffordance [\n" + + " a td:ActionAffordance ;\n" + + " dct:title \"Third Action\" ;\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasOperationType td:invokeAction;\n" + + " ] ;\n" + " ] ." ; - + TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, testTD); - + assertEquals(3, reader.readActions().size()); - + List actionTitles = reader.readActions().stream().map(action -> action.getTitle().get()) .collect(Collectors.toList()); - + assertTrue(actionTitles.contains("First Action")); assertTrue(actionTitles.contains("Second Action")); assertTrue(actionTitles.contains("Third Action")); } - + @Test public void testReadOneActionOneObjectInput() { String testSimpleObject = - " td:hasInputSchema [\n" + - " a js:ObjectSchema ;\n" + - " js:properties [\n" + + " td:hasInputSchema [\n" + + " a js:ObjectSchema ;\n" + + " js:properties [\n" + " a js:BooleanSchema ;\n" + " js:propertyName \"boolean_value\";\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:NumberSchema ;\n" + " js:propertyName \"number_value\";\n" + - " js:maximum 100.05 ;\n" + + " js:maximum 100.05 ;\n" + " js:minimum -100.05 ;\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:IntegerSchema ;\n" + " js:propertyName \"integer_value\";\n" + - " js:maximum 100 ;\n" + + " js:maximum 100 ;\n" + " js:minimum -100 ;\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:StringSchema ;\n" + " js:propertyName \"string_value\";\n" + " ] ;\n" + - " js:properties [\n" + + " js:properties [\n" + " a js:NullSchema ;\n" + " js:propertyName \"null_value\";\n" + " ] ;\n" + " js:required \"integer_value\", \"number_value\" ;\n" + " ]\n"; - - TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_IO_HEAD + testSimpleObject + + TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_IO_HEAD + testSimpleObject + TEST_IO_TAIL); - + ActionAffordance action = reader.readActions().get(0); - + Optional input = action.getInputSchema(); assertTrue(input.isPresent()); assertEquals(DataSchema.OBJECT, input.get().getDatatype()); - + ObjectSchema schema = (ObjectSchema) input.get(); assertEquals(5, schema.getProperties().size()); - + DataSchema booleanProperty = schema.getProperties().get("boolean_value"); assertEquals(DataSchema.BOOLEAN, booleanProperty.getDatatype()); - + DataSchema integerProperty = schema.getProperties().get("integer_value"); assertEquals(DataSchema.INTEGER, integerProperty.getDatatype()); assertEquals(-100, ((IntegerSchema) integerProperty).getMinimum().get().intValue()); assertEquals(100, ((IntegerSchema) integerProperty).getMaximum().get().intValue()); - + DataSchema numberProperty = schema.getProperties().get("number_value"); assertEquals(DataSchema.NUMBER, numberProperty.getDatatype()); assertEquals(-100.05, ((NumberSchema) numberProperty).getMinimum().get().doubleValue(), 0.001); assertEquals(100.05, ((NumberSchema) numberProperty).getMaximum().get().doubleValue(), 0.001); - + DataSchema stringProperty = schema.getProperties().get("string_value"); assertEquals(DataSchema.STRING, stringProperty.getDatatype()); - + DataSchema nullProperty = schema.getProperties().get("null_value"); assertEquals(DataSchema.NULL, nullProperty.getDatatype()); - + assertEquals(2, schema.getRequiredProperties().size()); assertTrue(schema.getRequiredProperties().contains("integer_value")); assertTrue(schema.getRequiredProperties().contains("number_value")); } - + @Test public void testReadTDFromFile() throws IOException { // Read a TD from a File by passing its path as parameter ThingDescription simple = TDGraphReader.readFromFile(TDFormat.RDF_TURTLE, "samples/simple_td.ttl"); ThingDescription forklift = TDGraphReader.readFromFile(TDFormat.RDF_TURTLE, "samples/forkliftRobot.ttl"); - + // Check if a TD was created from the file by checking its title assertEquals("My Thing", simple.getTitle()); assertEquals("forkliftRobot", forklift.getTitle()); } - + @Test public void testReadSimpleFullTD() { ThingDescription td = TDGraphReader.readFromString(TDFormat.RDF_TURTLE, TEST_SIMPLE_TD); - + // Check metadata assertEquals("My Thing", td.getTitle()); assertEquals("http://example.org/#thing", td.getThingURI().get()); @@ -503,61 +505,61 @@ public void testReadSimpleFullTD() { assertTrue(td.getSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType() .equals(WoTSec.NoSecurityScheme))); assertEquals(1, td.getActions().size()); - + // Check action metadata ActionAffordance action = td.getActions().get(0); assertEquals("My Action", action.getTitle().get()); assertEquals(1, action.getForms().size()); - + // Check action form Form form = action.getForms().get(0); assertForm(form, "PUT", "http://example.org/action", "application/json", TD.invokeAction); - + // Check action input data schema ObjectSchema input = (ObjectSchema) action.getInputSchema().get(); assertEquals(DataSchema.OBJECT, input.getDatatype()); assertEquals(1, input.getProperties().size()); assertEquals(1, input.getRequiredProperties().size()); - + assertEquals(DataSchema.NUMBER, input.getProperties().get("number_value").getDatatype()); assertTrue(input.getRequiredProperties().contains("number_value")); - + // Check action output data schema ObjectSchema output = (ObjectSchema) action.getOutputSchema().get(); assertEquals(DataSchema.OBJECT, output.getDatatype()); assertEquals(1, output.getProperties().size()); assertEquals(1, output.getRequiredProperties().size()); - + assertEquals(DataSchema.BOOLEAN, output.getProperties().get("boolean_value").getDatatype()); assertTrue(output.getRequiredProperties().contains("boolean_value")); } - + @Test public void testMissingMandatoryTitle() { String testTDWithMissingTitle = "@prefix td: .\n" + "@prefix wotsec: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + " td:hasBase .\n" ; - + Exception exception = assertThrows(InvalidTDException.class, () -> { TDGraphReader.readFromString(TDFormat.RDF_TURTLE, testTDWithMissingTitle); }); - + String expectedMessage = "Missing mandatory title."; String actualMessage = exception.getMessage(); - - assertTrue(actualMessage.contains(expectedMessage)); + + assertTrue(actualMessage.contains(expectedMessage)); } - - private void assertForm(Form form, String methodName, String target, + + private void assertForm(Form form, String methodName, String target, String contentType, String operationType) { assertEquals(methodName, form.getMethodName().get()); assertEquals(target, form.getTarget()); assertEquals(contentType, form.getContentType()); assertTrue(form.hasOperationType(operationType)); } - + } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/TDGraphWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriterTest.java similarity index 84% rename from src/test/java/ch/unisg/ics/interactions/wot/td/io/TDGraphWriterTest.java rename to src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriterTest.java index 4068b23b..5eaf9a4d 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/TDGraphWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriterTest.java @@ -1,4 +1,4 @@ -package ch.unisg.ics.interactions.wot.td.io; +package ch.unisg.ics.interactions.wot.td.io.graph; import static org.junit.Assert.assertTrue; @@ -7,6 +7,8 @@ import java.util.Arrays; import java.util.List; +import ch.unisg.ics.interactions.wot.td.io.graph.ReadWriteUtils; +import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphWriter; import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.ValueFactory; @@ -37,233 +39,233 @@ public class TDGraphWriterTest { private static final String THING_TITLE = "My Thing"; private static final String THING_IRI = "http://example.org/#thing"; private static final String IO_BASE_IRI = "http://example.org/"; - + private static final String PREFIXES = "@prefix td: .\n" + "@prefix htv: .\n" + "@prefix hctl: .\n" + "@prefix dct: .\n" + "@prefix wotsec: .\n" + - "@prefix js: .\n" + - "@prefix saref: .\n" + + "@prefix js: .\n" + + "@prefix saref: .\n" + "@prefix xsd: .\n"; - + @Test public void testNoThingURI() throws RDFParseException, RDFHandlerException, IOException { - String testTD = + String testTD = PREFIXES + "\n" + - "[] a td:Thing ;\n" + + "[] a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] .\n"; - + ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addSecurityScheme(new NoSecurityScheme()) .build(); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteTitle() throws RDFParseException, RDFHandlerException, IOException { String testTD = PREFIXES + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] .\n" ; - + ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addThingURI(THING_IRI) .addSecurityScheme(new NoSecurityScheme()) .build(); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteAPIKeySecurityScheme() throws RDFParseException, RDFHandlerException, IOException { String testTD = PREFIXES + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:APIKeySecurityScheme ;\n" + " wotsec:in \"HEADER\" ;\n" + " wotsec:name \"X-API-Key\" ;\n" + " ] .\n"; - + ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addThingURI(THING_IRI) .addSecurityScheme(new APIKeySecurityScheme(TokenLocation.HEADER, "X-API-Key")) .build(); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteAdditionalTypes() throws RDFParseException, RDFHandlerException, IOException { - String testTD = + String testTD = PREFIXES + "@prefix eve: .\n" + "@prefix iot: .\n" + "\n" + - " a td:Thing, eve:Artifact, iot:Light ;\n" + + " a td:Thing, eve:Artifact, iot:Light ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] .\n"; - + ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addThingURI(THING_IRI) .addSemanticType("http://w3id.org/eve#Artifact") .addSemanticType("http://iotschema.org/Light") .addSecurityScheme(new NoSecurityScheme()) .build(); - + assertIsomorphicGraphs(testTD, td); } - + @Test - public void testWriteTypesDeduplication() throws RDFParseException, RDFHandlerException, + public void testWriteTypesDeduplication() throws RDFParseException, RDFHandlerException, IOException { - - String testTD = + + String testTD = PREFIXES + "@prefix eve: .\n" + "\n" + - " a td:Thing, eve:Artifact ;\n" + + " a td:Thing, eve:Artifact ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] .\n"; - + ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addThingURI(THING_IRI) .addSemanticType("http://w3id.org/eve#Artifact") .addSemanticType("http://w3id.org/eve#Artifact") .addSecurityScheme(new NoSecurityScheme()) .build(); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteBaseURI() throws RDFParseException, RDFHandlerException, IOException { - String testTD = + String testTD = PREFIXES + "\n" + " a td:Thing ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ];\n" + " dct:title \"My Thing\" ;\n" + " td:hasBase .\n"; - + ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addThingURI(THING_IRI) .addBaseURI("http://example.org/") .build(); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteOnePropertyDefaultValues() throws RDFParseException, RDFHandlerException, IOException { String testTD = PREFIXES + "@prefix iot: .\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + - " td:hasPropertyAffordance [\n" + + " td:hasPropertyAffordance [\n" + " a td:PropertyAffordance, js:IntegerSchema, iot:MyProperty ;\n" + " td:name \"my_property\" ;\n" + " td:isObservable true ;\n" + - " td:hasForm [\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasOperationType td:readProperty, td:writeProperty;\n" + - " ] ;\n" + + " td:hasForm [\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasOperationType td:readProperty, td:writeProperty;\n" + + " ] ;\n" + " ] ." ; - - - PropertyAffordance property = new PropertyAffordance.Builder(new IntegerSchema.Builder().build(), + + + PropertyAffordance property = new PropertyAffordance.Builder(new IntegerSchema.Builder().build(), new Form.Builder("http://example.org/count").build()) .addSemanticType("http://iotschema.org/MyProperty") .addName("my_property") .addObserve() .build(); - + ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addThingURI(THING_IRI) .addSecurityScheme(new NoSecurityScheme()) .addProperty(property) .build(); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWritePropertySubprotocol() throws RDFParseException, RDFHandlerException, IOException { String testTD = PREFIXES + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + - " td:hasBase ;\n" + - " td:hasPropertyAffordance [\n" + + " td:hasBase ;\n" + + " td:hasPropertyAffordance [\n" + " a td:PropertyAffordance, js:IntegerSchema ;\n" + " td:isObservable true ;\n" + - " td:hasForm [\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + + " td:hasForm [\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + " hctl:hasOperationType td:readProperty, td:writeProperty;\n" + " hctl:forSubProtocol \"websub\";\n" + - " ] ;\n" + + " ] ;\n" + " ] ." ; - - - PropertyAffordance property = new PropertyAffordance.Builder(new IntegerSchema.Builder().build(), + + + PropertyAffordance property = new PropertyAffordance.Builder(new IntegerSchema.Builder().build(), new Form.Builder("http://example.org/count") .addSubProtocol("websub") .build()) .addObserve() .build(); - - ThingDescription td = constructThingDescription(new ArrayList(Arrays.asList(property)), + + ThingDescription td = constructThingDescription(new ArrayList(Arrays.asList(property)), new ArrayList(Arrays.asList())); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteOneAction() throws RDFParseException, RDFHandlerException, IOException { String testTD = PREFIXES + "@prefix iot: .\n" + "\n" + - " a td:Thing ;\n" + + " a td:Thing ;\n" + " dct:title \"My Thing\" ;\n" + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + - " td:hasBase ;\n" + - " td:hasActionAffordance [\n" + + " td:hasBase ;\n" + + " td:hasActionAffordance [\n" + " a td:ActionAffordance, iot:MyAction ;\n" + - " td:name \"my_action\" ;\n" + - " dct:title \"My Action\" ;\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\" ;\n" + - " hctl:hasTarget ;\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasOperationType td:invokeAction;\n" + - " ] ;\n" + - " td:hasInputSchema [\n" + - " a js:ObjectSchema ;\n" + - " js:properties [\n" + + " td:name \"my_action\" ;\n" + + " dct:title \"My Action\" ;\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\" ;\n" + + " hctl:hasTarget ;\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasOperationType td:invokeAction;\n" + + " ] ;\n" + + " td:hasInputSchema [\n" + + " a js:ObjectSchema ;\n" + + " js:properties [\n" + " a js:NumberSchema ;\n" + " js:propertyName \"number_value\";\n" + " ] ;\n" + " js:required \"number_value\" ;\n" + - " ] ;\n" + - " td:hasOutputSchema [\n" + - " a js:ObjectSchema ;\n" + - " js:properties [\n" + + " ] ;\n" + + " td:hasOutputSchema [\n" + + " a js:ObjectSchema ;\n" + + " js:properties [\n" + " a js:BooleanSchema ;\n" + " js:propertyName \"boolean_value\";\n" + " ] ;\n" + " js:required \"boolean_value\" ;\n" + - " ]\n" + + " ]\n" + " ] ." ; - + ActionAffordance simpleAction = new ActionAffordance.Builder( new Form.Builder( "http://example.org/action") .setMethodName("PUT") @@ -280,19 +282,19 @@ public void testWriteOneAction() throws RDFParseException, RDFHandlerException, .addRequiredProperties("boolean_value") .build()) .build(); - - ThingDescription td = constructThingDescription(new ArrayList(), + + ThingDescription td = constructThingDescription(new ArrayList(), new ArrayList(Arrays.asList(simpleAction))); - + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteAdditionalMetadata() throws RDFParseException, RDFHandlerException, IOException { String testTD = PREFIXES + "@prefix eve: .\n" + - " a td:Thing, saref:LightSwitch;\n" + - " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ];\n" + + " a td:Thing, saref:LightSwitch;\n" + + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ];\n" + " dct:title \"My Lamp Thing\" ;\n" + " eve:hasManual [ a eve:Manual;\n" + " dct:title \"My Lamp Manual\";\n" + @@ -301,19 +303,19 @@ public void testWriteAdditionalMetadata() throws RDFParseException, RDFHandlerEx " eve:hasLanguage \n" + " ]\n" + " ].\n" ; - + ValueFactory rdf = SimpleValueFactory.getInstance(); Model metadata = new LinkedHashModel(); - + final String NS = "http://w3id.org/eve#"; metadata.setNamespace("eve", NS); - + BNode manualId = rdf.createBNode(); BNode protocolId = rdf.createBNode(); metadata.add(rdf.createIRI("http://example.org/lamp123"), rdf.createIRI(NS,"hasManual"), manualId); metadata.add(manualId, RDF.TYPE, rdf.createIRI(NS, "Manual")); metadata.add(manualId, DCTERMS.TITLE, rdf.createLiteral("My Lamp Manual")); - + ThingDescription td = new ThingDescription.Builder("My Lamp Thing") .addThingURI("http://example.org/lamp123") .addSemanticType("https://saref.etsi.org/core/LightSwitch") @@ -322,39 +324,39 @@ public void testWriteAdditionalMetadata() throws RDFParseException, RDFHandlerEx .addGraph(metadata) .addGraph(new ModelBuilder() .add(manualId, rdf.createIRI(NS, "hasUsageProtocol"), protocolId) - .build()) + .build()) .addTriple(protocolId, rdf.createIRI(NS,"hasLanguage"), rdf.createIRI("http://jason.sourceforge.net/wp/description/")) .build(); - - assertIsomorphicGraphs(testTD, td); + + assertIsomorphicGraphs(testTD, td); } - + @Test public void testWriteReadmeExample() throws RDFParseException, RDFHandlerException, IOException { String testTD = PREFIXES + - " a td:Thing, saref:LightSwitch;\n" + - " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ];\n" + - " dct:title \"My Lamp Thing\" ;\n" + - " td:hasActionAffordance [ a td:ActionAffordance, saref:ToggleCommand;\n" + + " a td:Thing, saref:LightSwitch;\n" + + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ];\n" + + " dct:title \"My Lamp Thing\" ;\n" + + " td:hasActionAffordance [ a td:ActionAffordance, saref:ToggleCommand;\n" + " dct:title \"Toggle\";\n" + - " td:hasForm [\n" + - " htv:methodName \"PUT\";\n" + - " hctl:forContentType \"application/json\";\n" + - " hctl:hasTarget ;\n" + - " hctl:hasOperationType td:invokeAction\n" + - " ];\n" + + " td:hasForm [\n" + + " htv:methodName \"PUT\";\n" + + " hctl:forContentType \"application/json\";\n" + + " hctl:hasTarget ;\n" + + " hctl:hasOperationType td:invokeAction\n" + + " ];\n" + " td:hasInputSchema [ a saref:OnOffState, js:ObjectSchema;\n" + - " js:properties [ a js:BooleanSchema;\n" + - " js:propertyName \"status\"\n" + - " ];\n" + - " js:required \"status\"\n" + - " ];\n" + + " js:properties [ a js:BooleanSchema;\n" + + " js:propertyName \"status\"\n" + + " ];\n" + + " js:required \"status\"\n" + + " ];\n" + " ]."; - + Form toggleForm = new Form.Builder("http://mylamp.example.org/toggle") .setMethodName("PUT") .build(); - + ActionAffordance toggle = new ActionAffordance.Builder(toggleForm) .addTitle("Toggle") .addSemanticType("https://saref.etsi.org/core/ToggleCommand") @@ -365,21 +367,21 @@ public void testWriteReadmeExample() throws RDFParseException, RDFHandlerExcepti .addRequiredProperties("status") .build()) .build(); - + ThingDescription td = new ThingDescription.Builder("My Lamp Thing") .addThingURI("http://example.org/lamp123") .addSemanticType("https://saref.etsi.org/core/LightSwitch") .addAction(toggle) .build(); - + assertIsomorphicGraphs(testTD, td); } - - private void assertIsomorphicGraphs(String expectedTD, ThingDescription td) throws RDFParseException, + + private void assertIsomorphicGraphs(String expectedTD, ThingDescription td) throws RDFParseException, RDFHandlerException, IOException { - Model expectedModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, expectedTD, + Model expectedModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, expectedTD, IO_BASE_IRI); - + String description = new TDGraphWriter(td) .setNamespace("td", "https://www.w3.org/2019/wot/td#") .setNamespace("htv", "http://www.w3.org/2011/http#") @@ -389,31 +391,31 @@ private void assertIsomorphicGraphs(String expectedTD, ThingDescription td) thro .setNamespace("js", "https://www.w3.org/2019/wot/json-schema#") .setNamespace("saref", "https://saref.etsi.org/core/") .write(); - + System.out.println(description); - - Model tdModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, description, + + Model tdModel = ReadWriteUtils.readModelFromString(RDFFormat.TURTLE, description, IO_BASE_IRI); - + assertTrue(Models.isomorphic(expectedModel, tdModel)); - + } - - private ThingDescription constructThingDescription(List properties, + + private ThingDescription constructThingDescription(List properties, List actions) { ThingDescription.Builder builder = new ThingDescription.Builder(THING_TITLE) .addThingURI(THING_IRI) .addBaseURI("http://example.org/") .addSecurityScheme(new NoSecurityScheme()); - + for (PropertyAffordance property : properties) { builder.addProperty(property); } - + for (ActionAffordance action : actions) { builder.addAction(action); } - + return builder.build(); } } From a32102f24256c25ce1b64995c6c55c4ffbe294dd Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 16 Sep 2021 16:04:21 +0200 Subject: [PATCH 02/28] Implement TDJsonWriter --- build.gradle | 3 +- .../ics/interactions/wot/td/io/json/JWot.java | 38 ++++ .../wot/td/io/json/TDJsonWriter.java | 165 ++++++++++++++++-- 3 files changed, 188 insertions(+), 18 deletions(-) create mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java diff --git a/build.gradle b/build.gradle index 4d1c9c43..d656ec4d 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'maven-publish' java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 - + withJavadocJar() withSourcesJar() } @@ -20,6 +20,7 @@ dependencies { implementation 'org.apache.httpcomponents.client5:httpclient5:5.1' implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.1' implementation 'com.google.code.gson:gson:2.8.8' + implementation 'org.glassfish:javax.json:1.1.4' implementation 'org.eclipse.rdf4j:rdf4j-runtime:3.7.2' implementation 'org.slf4j:slf4j-api:2.0.0-alpha5' diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java new file mode 100644 index 00000000..c1cd59df --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java @@ -0,0 +1,38 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +public interface JWot { + + String WOT_CONTEXT = "https://www.w3.org/2019/wot/td/v1"; + + + String BASE = "base"; + String CONTEXT = "@context"; + + String TITLE = "title"; + String DESCRIPTION = "description"; + + String PROPERTIES = "properties"; + String OBSERVABLE = "observable"; + + String ACTIONS = "actions"; + String INPUT = "input"; + String OUTPUT = "output"; + String SAFE = "safe"; + String IDEMPOTENT = "idempotent"; + + String EVENTS = "events"; + + String SEMANTIC_TYPE = "@type"; + String TYPE = "type"; + + String FORMS = "forms"; + String TARGET = "href"; + String METHOD = "htv:methodName"; + String CONTENT_TYPE = "contentType"; + String SUBPROTOCOL = "subprotocol"; + String OPERATIONS = "op"; + + String SECURITY = "security"; + String SECURITY_DEF = "securityDefinitions"; + +} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 43620493..3a1f5b48 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -1,59 +1,190 @@ package ch.unisg.ics.interactions.wot.td.io.json; import ch.unisg.ics.interactions.wot.td.ThingDescription; +import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; +import ch.unisg.ics.interactions.wot.td.affordances.Form; +import ch.unisg.ics.interactions.wot.td.affordances.InteractionAffordance; +import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; import ch.unisg.ics.interactions.wot.td.io.AbstractTDWriter; +import javax.json.*; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + /** * A writer to serialize TDs in the JSON-LD 1.1 format. */ public class TDJsonWriter extends AbstractTDWriter { + private final JsonObjectBuilder document; + private final JsonArrayBuilder context; + private Optional semanticContext; + public TDJsonWriter(ThingDescription td) { super(td); + document = Json.createObjectBuilder(); + context = Json.createArrayBuilder(); + semanticContext = Optional.empty(); + context.add(JWot.WOT_CONTEXT); + document.add(JWot.CONTEXT, context); } @Override public String write() { - return null; + semanticContext.ifPresent(context::add); + this.addTitle() + .addTypes() + .addSecurity() + .addBaseURI() + .addProperties() + .addActions(); + //TODO .addGraph()??? + + OutputStream out = new ByteArrayOutputStream(); + JsonWriter writer = Json.createWriter(out); + writer.write(document.build()); + return out.toString(); } @Override - public AbstractTDWriter setNamespace(String prefix, String namespace) { - return null; + public TDJsonWriter setNamespace(String prefix, String namespace) { + if(semanticContext.isPresent()){ + semanticContext.get().add(prefix, namespace); + } else { + JsonObjectBuilder semContextBuilder = Json.createObjectBuilder(); + semContextBuilder.add(prefix, namespace); + semanticContext = Optional.of(semContextBuilder); + document.add(JWot.CONTEXT, semanticContext.get()); + } + + return this; } @Override - protected AbstractTDWriter addTypes() { - return null; + protected TDJsonWriter addTypes() { + //TODO This is ugly why is the types sometimes a set and sometimes a list? + document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); + return this; } @Override - protected AbstractTDWriter addTitle() { - return null; + protected TDJsonWriter addTitle() { + document.add(JWot.TITLE, td.getTitle()); + return this; } @Override - protected AbstractTDWriter addSecurity() { - return null; + protected TDJsonWriter addSecurity() { + //TODO implement: for the time being ignores security schemes and puts NoSecurityScheme + //Add security def + + //Add actual security field + + return this; } @Override - protected AbstractTDWriter addBaseURI() { - return null; + protected TDJsonWriter addBaseURI() { + td.getBaseURI().ifPresent(uri -> document.add(JWot.BASE, uri)); + return this; } @Override - protected AbstractTDWriter addProperties() { - return null; + protected TDJsonWriter addProperties() { + document.add(JWot.PROPERTIES, this.getAffordancesObject(td.getProperties(), this::getProperty)); + return this; } @Override - protected AbstractTDWriter addActions() { - return null; + protected TDJsonWriter addActions() { + document.add(JWot.ACTIONS, this.getAffordancesObject(td.getActions(), this::getAction)); + return this; } @Override - protected AbstractTDWriter addGraph() { - return null; + protected TDJsonWriter addGraph() { + //TODO I don't know if I need to implement this + return this; + } + + private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { + if (affordances.size() > 0) { + JsonObjectBuilder rootObj = Json.createObjectBuilder(); + affordances.forEach(aff -> + rootObj.add(aff.getTitle().get(), mapper.apply(aff)) + ); + return rootObj; + } + return Json.createObjectBuilder(); //empty + } + + private JsonObjectBuilder getProperty(PropertyAffordance prop) { + JsonObjectBuilder propertyObj = getAffordance(prop) + .add(JWot.OBSERVABLE, prop.isObservable()) + .add(JWot.TYPE, prop.getDataSchema().getDatatype()); + //TODO add DataSchema + //but how? + return propertyObj; + } + + private JsonObjectBuilder getAction(ActionAffordance affordance) { + //add action related fields + //TODO safe and idempotent are missing in the model + //TODO add input-output Schema + + return Json.createObjectBuilder(); + } + + + private JsonArrayBuilder getSemanticTypes(List semanticTypes) { + JsonArrayBuilder types = Json.createArrayBuilder(); + semanticTypes.forEach(types::add); + return types; + } + + private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { + JsonObjectBuilder affordanceObj = Json.createObjectBuilder(); + + //add semantic type(s) + affordanceObj.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(affordance.getSemanticTypes())); + + //add readable name + affordance.getName().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); + + //TODO description is missing in the model + //affordance.getName().ifPresent(n -> affordanceObj.addProperty(JWot.DESCRIPTION, n)); + + //add forms + affordanceObj.add(JWot.FORMS, this.getFormsArray(affordance.getForms())); + + return affordanceObj; + } + + private JsonArrayBuilder getFormsArray(List forms) { + JsonArrayBuilder formArray = Json.createArrayBuilder(); + forms.forEach(form -> { + JsonObjectBuilder formObj = Json.createObjectBuilder() + .add(JWot.TARGET, form.getTarget()) + .add(JWot.CONTENT_TYPE, form.getContentType()); + + form.getSubProtocol().ifPresent(sub -> formObj.add(JWot.SUBPROTOCOL, sub)); + + //Add operations + JsonArrayBuilder opArray = Json.createArrayBuilder(); + form.getOperationTypes().forEach(opArray::add); + formObj.add(JWot.OPERATIONS, opArray); + + //Add methodName only if there is one operation type to avoid ambiguity + form.getMethodName().ifPresent(m -> { + if(form.getOperationTypes().size() == 1) + formObj.add(JWot.METHOD, m); + }); + formArray.add(formObj); + }); + return formArray; } } From 92a79c01db5a701feda21cd6c8330e14ef06eac1 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 16 Sep 2021 17:02:46 +0200 Subject: [PATCH 03/28] Fix typo --- .../wot/td/schemas/ObjectSchema.java | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/schemas/ObjectSchema.java b/src/main/java/ch/unisg/ics/interactions/wot/td/schemas/ObjectSchema.java index 6f54135c..3a392a59 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/schemas/ObjectSchema.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/schemas/ObjectSchema.java @@ -18,45 +18,45 @@ public class ObjectSchema extends DataSchema { final private Map properties; final private List required; - - protected ObjectSchema(Set semanticTypes, Set enumeration, + + protected ObjectSchema(Set semanticTypes, Set enumeration, Map properties, List required) { super(DataSchema.OBJECT, semanticTypes, enumeration); - + this.properties = properties; this.required = required; } - + public boolean validate(Map values) { // TODO return true; } - + @Override public Object parseJson(JsonElement element) { if (!element.isJsonObject()) { throw new IllegalArgumentException("The payload is not an object."); } - + JsonObject objPayload = element.getAsJsonObject(); Map data = new HashMap(); - + for (String propName : properties.keySet()) { JsonElement prop = objPayload.get(propName); if (prop == null) { if (hasRequiredProperty(propName)) { throw new IllegalArgumentException("Missing required property: " + propName); } - + continue; } - + DataSchema propSchema = properties.get(propName); - + // Filter out data schema tags, if any - List tags = propSchema.getSemanticTypes().stream().filter(tag -> + List tags = propSchema.getSemanticTypes().stream().filter(tag -> !tag.startsWith(JSONSchema.PREFIX)).collect(Collectors.toList()); - + if (tags.isEmpty()) { data.put(propName, properties.get(propName).parseJson(prop)); } else { @@ -65,44 +65,44 @@ public Object parseJson(JsonElement element) { data.put(semanticType, properties.get(propName).parseJson(prop)); } } - + return data; } - + public Map instantiate(Map values) { Map instance = new HashMap(); - + // TODO: handle semantic arrays // TODO: handle semantic arrays with semantic elements - + for (String tag : values.keySet()) { - Optional propertyName = getFirstPropertyNameBySemnaticType(tag); - + Optional propertyName = getFirstPropertyNameBySemanticType(tag); + if (propertyName.isPresent()) { instance.put(propertyName.get(), values.get(tag)); } else if (properties.containsKey(tag)) { instance.put(tag, values.get(tag)); } } - + return instance; } - + public Optional getProperty(String propertyName) { DataSchema schema = properties.get(propertyName); - return (schema == null) ? Optional.empty() : Optional.of(schema); + return (schema == null) ? Optional.empty() : Optional.of(schema); } - - public Optional getFirstPropertyNameBySemnaticType(String type) { + + public Optional getFirstPropertyNameBySemanticType(String type) { for (Map.Entry property : properties.entrySet()) { if (property.getValue().isA(type)) { return Optional.of(property.getKey()); } } - + return Optional.empty(); } - + public Map getProperties() { return properties; } @@ -110,7 +110,7 @@ public Map getProperties() { public List getRequiredProperties() { return required; } - + public boolean hasRequiredProperty(String propName) { return required.contains(propName); } @@ -118,30 +118,30 @@ public boolean hasRequiredProperty(String propName) { public static class Builder extends DataSchema.Builder { final private Map properties; final private List required; - + public Builder() { this.properties = new HashMap(); this.required = new ArrayList(); } - + public Builder addProperty(String propertyName, DataSchema schema) { this.properties.put(propertyName, schema); return this; } - + public Builder addRequiredProperties(String... properties) { this.required.addAll(Arrays.asList(properties)); return this; } - + public ObjectSchema build() throws InvalidTDException { for (String propertyName : required) { if (!properties.containsKey(propertyName)) { - throw new InvalidTDException("Required property is not in the list of properties: " + throw new InvalidTDException("Required property is not in the list of properties: " + propertyName); } } - + return new ObjectSchema(semanticTypes, enumeration, properties, required); } } From 040d3ddf92e65bf3e620940707e053ec78b6bd17 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 16 Sep 2021 17:03:10 +0200 Subject: [PATCH 04/28] Implement SchemaJsonWriter --- .../wot/td/io/json/SchemaJsonWriter.java | 70 +++++++++++++++++++ .../wot/td/io/json/TDJsonWriter.java | 27 ++++--- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java index 3ad7354a..fc970ebb 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java @@ -1,4 +1,74 @@ package ch.unisg.ics.interactions.wot.td.io.json; +import ch.unisg.ics.interactions.wot.td.schemas.*; +import ch.unisg.ics.interactions.wot.td.vocabularies.JSONSchema; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; + public class SchemaJsonWriter { + + public static JsonObjectBuilder getDataSchema(DataSchema schema) { + switch (schema.getDatatype()) { + case DataSchema.OBJECT: + return getObjectSchema((ObjectSchema) schema); + case DataSchema.ARRAY: + return getArraySchema((ArraySchema) schema); + case DataSchema.BOOLEAN: + return getSimpleSchema(DataSchema.BOOLEAN); + case DataSchema.INTEGER: + return getNumberSchema((NumberSchema) schema, DataSchema.INTEGER); + case DataSchema.NUMBER: + return getNumberSchema((NumberSchema) schema, DataSchema.NUMBER); + case DataSchema.STRING: + return getSimpleSchema(DataSchema.STRING); + case DataSchema.NULL: + return getSimpleSchema(DataSchema.NULL); + default: + return Json.createObjectBuilder(); + } + } + + private static JsonObjectBuilder getSimpleSchema(String schemaType) { + return Json.createObjectBuilder().add(JWot.TYPE, schemaType); + } + + private static JsonObjectBuilder getNumberSchema(NumberSchema schema, String type) { + JsonObjectBuilder schemaObj = getSimpleSchema(type); + if(type.equals(DataSchema.INTEGER)){ + //TODO should this be here or in the model class? Not touching that for the moment + schema.getMaximum().ifPresent(max -> schemaObj.add("maximum", max.intValue())); + schema.getMinimum().ifPresent(min -> schemaObj.add("maximum", min.intValue())); + } else { + schema.getMaximum().ifPresent(max -> schemaObj.add("maximum", max)); + schema.getMinimum().ifPresent(min -> schemaObj.add("maximum", min)); + } + return schemaObj; + } + + private static JsonObjectBuilder getArraySchema(ArraySchema schema) { + JsonObjectBuilder schemaObj = getSimpleSchema(DataSchema.ARRAY); + schema.getMinItems().ifPresent(min -> schemaObj.add("minItems", min)); + schema.getMaxItems().ifPresent(max -> schemaObj.add("maxItems", max)); + + JsonArrayBuilder itemsArray = Json.createArrayBuilder(); + schema.getItems().forEach(d -> itemsArray.add(getDataSchema(d))); + schemaObj.add("items", itemsArray); + return schemaObj; + } + + private static JsonObjectBuilder getObjectSchema(ObjectSchema schema) { + JsonObjectBuilder schemaObj = getSimpleSchema(DataSchema.OBJECT); + + JsonObjectBuilder propObj = Json.createObjectBuilder(); + schema.getProperties().forEach((k,v) -> propObj.add(k, getDataSchema(v))); + schemaObj.add("properties", propObj); + + JsonArrayBuilder requiredArray = Json.createArrayBuilder(); + schema.getRequiredProperties().forEach(requiredArray::add); + schemaObj.add("required", requiredArray); + + return schemaObj; + } } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 3a1f5b48..8f6f5ffd 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -81,8 +81,12 @@ protected TDJsonWriter addTitle() { protected TDJsonWriter addSecurity() { //TODO implement: for the time being ignores security schemes and puts NoSecurityScheme //Add security def - + document.add("securityDefinitions", + Json.createObjectBuilder().add("nosec_sc", + Json.createObjectBuilder().add("scheme", "nosec" )) + ); //Add actual security field + document.add("security", Json.createArrayBuilder().add("nosec")); return this; } @@ -124,19 +128,25 @@ private JsonObjectBuilder getAffordancesObject( private JsonObjectBuilder getProperty(PropertyAffordance prop) { JsonObjectBuilder propertyObj = getAffordance(prop) - .add(JWot.OBSERVABLE, prop.isObservable()) - .add(JWot.TYPE, prop.getDataSchema().getDatatype()); - //TODO add DataSchema - //but how? + .add(JWot.OBSERVABLE, prop.isObservable()); + JsonObjectBuilder dataSchema = SchemaJsonWriter.getDataSchema(prop.getDataSchema()); + propertyObj.addAll(dataSchema); return propertyObj; } private JsonObjectBuilder getAction(ActionAffordance affordance) { - //add action related fields + JsonObjectBuilder actionObj = Json.createObjectBuilder(); + //TODO safe and idempotent are missing in the model - //TODO add input-output Schema - return Json.createObjectBuilder(); + affordance.getInputSchema().ifPresent(d -> + actionObj.add(JWot.INPUT, SchemaJsonWriter.getDataSchema(d)) + ); + affordance.getOutputSchema().ifPresent(d -> + actionObj.add(JWot.OUTPUT, SchemaJsonWriter.getDataSchema(d)) + ); + + return actionObj; } @@ -156,7 +166,6 @@ private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { affordance.getName().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); //TODO description is missing in the model - //affordance.getName().ifPresent(n -> affordanceObj.addProperty(JWot.DESCRIPTION, n)); //add forms affordanceObj.add(JWot.FORMS, this.getFormsArray(affordance.getForms())); From af6aff93ae525dbce0dde01a7cc0a4c260b0ad32 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 16 Sep 2021 18:23:30 +0200 Subject: [PATCH 05/28] Start working on some test for the SchemaJsonWriter --- .../wot/td/io/json/SchemaJsonWriter.java | 20 ++--- .../wot/td/io/json/TDJsonWriter.java | 11 ++- .../wot/td/io/json/SchemaJsonWriterTest.java | 83 +++++++++++++++++++ .../wot/td/io/json/TDJsonWriterTest.java | 7 ++ 4 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java create mode 100644 src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java index fc970ebb..3e66a165 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java @@ -1,7 +1,6 @@ package ch.unisg.ics.interactions.wot.td.io.json; import ch.unisg.ics.interactions.wot.td.schemas.*; -import ch.unisg.ics.interactions.wot.td.vocabularies.JSONSchema; import javax.json.Json; import javax.json.JsonArrayBuilder; @@ -18,7 +17,7 @@ public static JsonObjectBuilder getDataSchema(DataSchema schema) { case DataSchema.BOOLEAN: return getSimpleSchema(DataSchema.BOOLEAN); case DataSchema.INTEGER: - return getNumberSchema((NumberSchema) schema, DataSchema.INTEGER); + return getIntegerSchema((IntegerSchema) schema, DataSchema.INTEGER); case DataSchema.NUMBER: return getNumberSchema((NumberSchema) schema, DataSchema.NUMBER); case DataSchema.STRING: @@ -36,14 +35,15 @@ private static JsonObjectBuilder getSimpleSchema(String schemaType) { private static JsonObjectBuilder getNumberSchema(NumberSchema schema, String type) { JsonObjectBuilder schemaObj = getSimpleSchema(type); - if(type.equals(DataSchema.INTEGER)){ - //TODO should this be here or in the model class? Not touching that for the moment - schema.getMaximum().ifPresent(max -> schemaObj.add("maximum", max.intValue())); - schema.getMinimum().ifPresent(min -> schemaObj.add("maximum", min.intValue())); - } else { - schema.getMaximum().ifPresent(max -> schemaObj.add("maximum", max)); - schema.getMinimum().ifPresent(min -> schemaObj.add("maximum", min)); - } + schema.getMaximum().ifPresent(max -> schemaObj.add("maximum", max)); + schema.getMinimum().ifPresent(min -> schemaObj.add("minimum", min)); + return schemaObj; + } + + private static JsonObjectBuilder getIntegerSchema(IntegerSchema schema, String type) { + JsonObjectBuilder schemaObj = getSimpleSchema(type); + schema.getMaximumAsInteger().ifPresent(max -> schemaObj.add("maximum", max)); + schema.getMinimumAsInteger().ifPresent(min -> schemaObj.add("minimum", min)); return schemaObj; } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 8f6f5ffd..55a96ed3 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -41,8 +41,8 @@ public String write() { .addSecurity() .addBaseURI() .addProperties() - .addActions(); - //TODO .addGraph()??? + .addActions() + .addGraph(); OutputStream out = new ByteArrayOutputStream(); JsonWriter writer = Json.createWriter(out); @@ -111,7 +111,12 @@ protected TDJsonWriter addActions() { @Override protected TDJsonWriter addGraph() { - //TODO I don't know if I need to implement this + td.getGraph().ifPresent(g -> { + g.getStatements(null, null, null).forEach(statement -> { + //TODO I'm not sure this is the right way to parse the statement + document.add(statement.getPredicate().stringValue(), statement.getObject().stringValue()); + }); + }); return this; } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java new file mode 100644 index 00000000..53040fac --- /dev/null +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java @@ -0,0 +1,83 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +import ch.unisg.ics.interactions.wot.td.schemas.*; +import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; + +import javax.json.Json; +import javax.json.JsonObject; + +public class SchemaJsonWriterTest extends TestCase { + + private final static String PREFIX = "https://example.org/#"; + + private static DataSchema semanticObjectSchema; + + @Test + public void testWriteStringSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "string") + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new StringSchema.Builder().build() + ).build(); + assertEquals(expected, test); + } + + @Test + public void testBooleanSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "boolean") + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new BooleanSchema.Builder().build() + ).build(); + assertEquals(expected, test); + } + + @Test + public void testNullSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "null") + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new NullSchema.Builder().build() + ).build(); + assertEquals(expected, test); + } + + @Test + public void testIntegerSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "integer") + .add("minimum", 5) + .add("maximum", 10) + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new IntegerSchema.Builder() + .addMaximum(10) + .addMinimum(5) + .build() + ).build(); + assertEquals(expected, test); + } + + @Test + public void testNumberSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "number") + .add("minimum", 5.5) + .add("maximum", 10.5) + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new NumberSchema.Builder() + .addMaximum(10.5) + .addMinimum(5.5) + .build() + ).build(); + assertEquals(expected, test); + } + + +} diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java new file mode 100644 index 00000000..b825c62d --- /dev/null +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -0,0 +1,7 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +import junit.framework.TestCase; + +public class TDJsonWriterTest extends TestCase { + +} From 47f496fce4bae62afa2a02101dcfb83095464ea8 Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 10:27:04 +0200 Subject: [PATCH 06/28] Completed SchemaJsonWriter tests --- .../wot/td/io/json/SchemaJsonWriter.java | 53 ++++++---- .../wot/td/io/json/TDJsonWriter.java | 4 +- .../wot/td/io/json/SchemaJsonWriterTest.java | 99 ++++++++++++++++++- 3 files changed, 129 insertions(+), 27 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java index 3e66a165..a7243c37 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java @@ -15,60 +15,73 @@ public static JsonObjectBuilder getDataSchema(DataSchema schema) { case DataSchema.ARRAY: return getArraySchema((ArraySchema) schema); case DataSchema.BOOLEAN: - return getSimpleSchema(DataSchema.BOOLEAN); + return getSimpleSchema(schema, DataSchema.BOOLEAN); case DataSchema.INTEGER: - return getIntegerSchema((IntegerSchema) schema, DataSchema.INTEGER); + return getIntegerSchema((IntegerSchema) schema); case DataSchema.NUMBER: - return getNumberSchema((NumberSchema) schema, DataSchema.NUMBER); + return getNumberSchema((NumberSchema) schema); case DataSchema.STRING: - return getSimpleSchema(DataSchema.STRING); + return getSimpleSchema(schema, DataSchema.STRING); case DataSchema.NULL: - return getSimpleSchema(DataSchema.NULL); + return getSimpleSchema(schema, DataSchema.NULL); default: return Json.createObjectBuilder(); } } - private static JsonObjectBuilder getSimpleSchema(String schemaType) { - return Json.createObjectBuilder().add(JWot.TYPE, schemaType); + private static JsonObjectBuilder getSimpleSchema(DataSchema schema, String schemaType) { + JsonObjectBuilder obj = Json.createObjectBuilder(); + if(schema.getSemanticTypes().size() > 1){ + JsonArrayBuilder typeArray = Json.createArrayBuilder(); + schema.getSemanticTypes().forEach(typeArray::add); + obj.add(JWot.SEMANTIC_TYPE, typeArray); + } else if (schema.getSemanticTypes().size() > 0){ + obj.add(JWot.SEMANTIC_TYPE, schema.getSemanticTypes().stream().findFirst().orElse("")); + } + return obj.add(JWot.TYPE, schemaType); } - private static JsonObjectBuilder getNumberSchema(NumberSchema schema, String type) { - JsonObjectBuilder schemaObj = getSimpleSchema(type); + private static JsonObjectBuilder getNumberSchema(NumberSchema schema) { + JsonObjectBuilder schemaObj = getSimpleSchema(schema, DataSchema.NUMBER); schema.getMaximum().ifPresent(max -> schemaObj.add("maximum", max)); schema.getMinimum().ifPresent(min -> schemaObj.add("minimum", min)); return schemaObj; } - private static JsonObjectBuilder getIntegerSchema(IntegerSchema schema, String type) { - JsonObjectBuilder schemaObj = getSimpleSchema(type); + private static JsonObjectBuilder getIntegerSchema(IntegerSchema schema) { + JsonObjectBuilder schemaObj = getSimpleSchema(schema, DataSchema.INTEGER); schema.getMaximumAsInteger().ifPresent(max -> schemaObj.add("maximum", max)); schema.getMinimumAsInteger().ifPresent(min -> schemaObj.add("minimum", min)); return schemaObj; } private static JsonObjectBuilder getArraySchema(ArraySchema schema) { - JsonObjectBuilder schemaObj = getSimpleSchema(DataSchema.ARRAY); + JsonObjectBuilder schemaObj = getSimpleSchema(schema, DataSchema.ARRAY); schema.getMinItems().ifPresent(min -> schemaObj.add("minItems", min)); schema.getMaxItems().ifPresent(max -> schemaObj.add("maxItems", max)); - JsonArrayBuilder itemsArray = Json.createArrayBuilder(); - schema.getItems().forEach(d -> itemsArray.add(getDataSchema(d))); - schemaObj.add("items", itemsArray); + if(schema.getItems().size() > 1){ + JsonArrayBuilder itemsArray = Json.createArrayBuilder(); + schema.getItems().forEach(d -> itemsArray.add(getDataSchema(d))); + } else if(schema.getItems().size() > 0){ + schemaObj.add("items", getDataSchema(schema.getItems().get(0))); + } + return schemaObj; } private static JsonObjectBuilder getObjectSchema(ObjectSchema schema) { - JsonObjectBuilder schemaObj = getSimpleSchema(DataSchema.OBJECT); + JsonObjectBuilder schemaObj = getSimpleSchema(schema, DataSchema.OBJECT); JsonObjectBuilder propObj = Json.createObjectBuilder(); schema.getProperties().forEach((k,v) -> propObj.add(k, getDataSchema(v))); schemaObj.add("properties", propObj); - JsonArrayBuilder requiredArray = Json.createArrayBuilder(); - schema.getRequiredProperties().forEach(requiredArray::add); - schemaObj.add("required", requiredArray); - + if(schema.getRequiredProperties().size()>0) { + JsonArrayBuilder requiredArray = Json.createArrayBuilder(); + schema.getRequiredProperties().forEach(requiredArray::add); + schemaObj.add("required", requiredArray); + } return schemaObj; } } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 55a96ed3..5602a94c 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -81,12 +81,12 @@ protected TDJsonWriter addTitle() { protected TDJsonWriter addSecurity() { //TODO implement: for the time being ignores security schemes and puts NoSecurityScheme //Add security def - document.add("securityDefinitions", + document.add(JWot.SECURITY_DEF, Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec" )) ); //Add actual security field - document.add("security", Json.createArrayBuilder().add("nosec")); + document.add(JWot.SECURITY, Json.createArrayBuilder().add("nosec")); return this; } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java index 53040fac..acc1f45b 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java @@ -2,7 +2,6 @@ import ch.unisg.ics.interactions.wot.td.schemas.*; import junit.framework.TestCase; -import org.junit.Before; import org.junit.Test; import javax.json.Json; @@ -10,10 +9,6 @@ public class SchemaJsonWriterTest extends TestCase { - private final static String PREFIX = "https://example.org/#"; - - private static DataSchema semanticObjectSchema; - @Test public void testWriteStringSchema() { JsonObject expected = Json.createObjectBuilder() @@ -79,5 +74,99 @@ public void testNumberSchema() { assertEquals(expected, test); } + @Test + public void testSimpleArraySchema(){ + JsonObject expected = Json.createObjectBuilder() + .add("type", "array") + .add("items", Json.createObjectBuilder().add("type", "string")) + .add("minItems", 1) + .add("maxItems", 10) + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new ArraySchema.Builder() + .addItem(new StringSchema.Builder().build()) + .addMaxItems(10) + .addMinItems(1) + .build() + ).build(); + + assertEquals(expected, test); + } + + @Test + public void testSimpleObjectSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "object") + .add("properties", Json.createObjectBuilder() + .add("name", Json.createObjectBuilder() + .add("type", "string")) + ).build(); + + JsonObject test = SchemaJsonWriter.getDataSchema( + new ObjectSchema.Builder() + .addProperty("name", new StringSchema.Builder().build()) + .build() + ).build(); + + assertEquals(expected,test); + } + + @Test + public void testSimpleObjectSchemaWithRequired() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "object") + .add("properties", Json.createObjectBuilder() + .add("name", Json.createObjectBuilder() + .add("type", "string") + ).add("age", Json.createObjectBuilder() + .add("type", "integer") + .add("miniumum", 0) + ) + ).add("required", Json.createArrayBuilder() + .add("name").add("age") + ).build(); + + JsonObject test = SchemaJsonWriter.getDataSchema( + new ObjectSchema.Builder() + .addProperty("name", new StringSchema.Builder().build()) + .addProperty("age", new IntegerSchema.Builder().addMinimum(0).build()) + .addRequiredProperties("name", "age") + .build() + ).build(); + + assertEquals(expected,test); + } + + @Test + public void testSemanticObject(){ + JsonObject expected = Json.createObjectBuilder() + .add("@type", Json.createArrayBuilder().add("sem:employee").add("sem:person")) + .add("type", "object") + .add("properties", Json.createObjectBuilder() + .add("name", Json.createObjectBuilder() + .add("@type", "sem:name") + .add("type", "string") + ).add("age", Json.createObjectBuilder() + .add("@type", "sem:age") + .add("type", "integer") + .add("minimum", 0) + ) + ).add("required", Json.createArrayBuilder() + .add("name").add("age") + ).build(); + + JsonObject test = SchemaJsonWriter.getDataSchema( + new ObjectSchema.Builder() + .addSemanticType("sem:person") + .addSemanticType("sem:employee") + .addProperty("name", new StringSchema.Builder().addSemanticType("sem:name").build()) + .addProperty("age", new IntegerSchema.Builder().addSemanticType("sem:age").addMinimum(0).build()) + .addRequiredProperties("name", "age") + .build() + ).build(); + + assertEquals(expected,test); + } + } From da0a18e2793ed65a0aafb9600f71d702a51f53f7 Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 14:01:55 +0200 Subject: [PATCH 07/28] Working on TDJsonWriter tests --- .../wot/td/io/json/TDJsonWriter.java | 68 ++++--- .../wot/td/io/json/SchemaJsonWriterTest.java | 23 +-- .../wot/td/io/json/TDJsonWriterTest.java | 190 +++++++++++++++++- 3 files changed, 239 insertions(+), 42 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 5602a94c..8252e155 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -21,21 +21,23 @@ public class TDJsonWriter extends AbstractTDWriter { private final JsonObjectBuilder document; - private final JsonArrayBuilder context; private Optional semanticContext; public TDJsonWriter(ThingDescription td) { super(td); document = Json.createObjectBuilder(); - context = Json.createArrayBuilder(); semanticContext = Optional.empty(); - context.add(JWot.WOT_CONTEXT); - document.add(JWot.CONTEXT, context); } - @Override - public String write() { - semanticContext.ifPresent(context::add); + public JsonObject getJson(){ + if(semanticContext.isPresent()){ + document.add(JWot.CONTEXT, Json.createArrayBuilder() + .add(JWot.WOT_CONTEXT) + .add(semanticContext.get())); + } else { + document.add(JWot.CONTEXT, JWot.WOT_CONTEXT); + } + this.addTitle() .addTypes() .addSecurity() @@ -43,10 +45,14 @@ public String write() { .addProperties() .addActions() .addGraph(); + return document.build(); + } + @Override + public String write() { OutputStream out = new ByteArrayOutputStream(); JsonWriter writer = Json.createWriter(out); - writer.write(document.build()); + writer.write(this.getJson()); return out.toString(); } @@ -55,10 +61,9 @@ public TDJsonWriter setNamespace(String prefix, String namespace) { if(semanticContext.isPresent()){ semanticContext.get().add(prefix, namespace); } else { - JsonObjectBuilder semContextBuilder = Json.createObjectBuilder(); - semContextBuilder.add(prefix, namespace); - semanticContext = Optional.of(semContextBuilder); - document.add(JWot.CONTEXT, semanticContext.get()); + JsonObjectBuilder semContextObj = Json.createObjectBuilder() + .add(prefix, namespace); + semanticContext = Optional.of(semContextObj); } return this; @@ -67,27 +72,30 @@ public TDJsonWriter setNamespace(String prefix, String namespace) { @Override protected TDJsonWriter addTypes() { //TODO This is ugly why is the types sometimes a set and sometimes a list? - document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); + if(td.getSemanticTypes().size() > 0) { + document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); + } return this; } @Override protected TDJsonWriter addTitle() { document.add(JWot.TITLE, td.getTitle()); + td.getThingURI().ifPresent(iri -> document.add("id", iri)); return this; } @Override protected TDJsonWriter addSecurity() { //TODO implement: for the time being ignores security schemes and puts NoSecurityScheme + // because I don't know ho to serialize them from the model //Add security def document.add(JWot.SECURITY_DEF, Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec" )) ); //Add actual security field - document.add(JWot.SECURITY, Json.createArrayBuilder().add("nosec")); - + document.add(JWot.SECURITY, Json.createArrayBuilder().add("nosec_sc")); return this; } @@ -99,24 +107,26 @@ protected TDJsonWriter addBaseURI() { @Override protected TDJsonWriter addProperties() { - document.add(JWot.PROPERTIES, this.getAffordancesObject(td.getProperties(), this::getProperty)); + if(td.getProperties().size() > 0){ + document.add(JWot.PROPERTIES, this.getAffordancesObject(td.getProperties(), this::getProperty)); + } return this; } @Override protected TDJsonWriter addActions() { - document.add(JWot.ACTIONS, this.getAffordancesObject(td.getActions(), this::getAction)); + if(td.getActions().size() > 0) { + document.add(JWot.ACTIONS, this.getAffordancesObject(td.getActions(), this::getAction)); + } return this; } @Override protected TDJsonWriter addGraph() { - td.getGraph().ifPresent(g -> { - g.getStatements(null, null, null).forEach(statement -> { - //TODO I'm not sure this is the right way to parse the statement - document.add(statement.getPredicate().stringValue(), statement.getObject().stringValue()); - }); - }); + td.getGraph().ifPresent(g -> g.getStatements(null, null, null).forEach(statement -> { + //TODO I'm not sure this is the right way to parse the statement + document.add(statement.getPredicate().stringValue(), statement.getObject().stringValue()); + })); return this; } @@ -139,15 +149,15 @@ private JsonObjectBuilder getProperty(PropertyAffordance prop) { return propertyObj; } - private JsonObjectBuilder getAction(ActionAffordance affordance) { - JsonObjectBuilder actionObj = Json.createObjectBuilder(); + private JsonObjectBuilder getAction(ActionAffordance action) { + JsonObjectBuilder actionObj = getAffordance(action); //TODO safe and idempotent are missing in the model - affordance.getInputSchema().ifPresent(d -> + action.getInputSchema().ifPresent(d -> actionObj.add(JWot.INPUT, SchemaJsonWriter.getDataSchema(d)) ); - affordance.getOutputSchema().ifPresent(d -> + action.getOutputSchema().ifPresent(d -> actionObj.add(JWot.OUTPUT, SchemaJsonWriter.getDataSchema(d)) ); @@ -165,7 +175,9 @@ private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { JsonObjectBuilder affordanceObj = Json.createObjectBuilder(); //add semantic type(s) - affordanceObj.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(affordance.getSemanticTypes())); + if(affordance.getSemanticTypes().size() > 0) { + affordanceObj.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(affordance.getSemanticTypes())); + } //add readable name affordance.getName().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java index acc1f45b..b7dfa078 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java @@ -1,13 +1,13 @@ package ch.unisg.ics.interactions.wot.td.io.json; import ch.unisg.ics.interactions.wot.td.schemas.*; -import junit.framework.TestCase; +import org.junit.Assert; import org.junit.Test; import javax.json.Json; import javax.json.JsonObject; -public class SchemaJsonWriterTest extends TestCase { +public class SchemaJsonWriterTest { @Test public void testWriteStringSchema() { @@ -17,7 +17,7 @@ public void testWriteStringSchema() { JsonObject test = SchemaJsonWriter.getDataSchema( new StringSchema.Builder().build() ).build(); - assertEquals(expected, test); + Assert.assertEquals(expected, test); } @Test @@ -28,7 +28,7 @@ public void testBooleanSchema() { JsonObject test = SchemaJsonWriter.getDataSchema( new BooleanSchema.Builder().build() ).build(); - assertEquals(expected, test); + Assert.assertEquals(expected, test); } @Test @@ -39,7 +39,7 @@ public void testNullSchema() { JsonObject test = SchemaJsonWriter.getDataSchema( new NullSchema.Builder().build() ).build(); - assertEquals(expected, test); + Assert.assertEquals(expected, test); } @Test @@ -55,7 +55,7 @@ public void testIntegerSchema() { .addMinimum(5) .build() ).build(); - assertEquals(expected, test); + Assert.assertEquals(expected, test); } @Test @@ -71,7 +71,7 @@ public void testNumberSchema() { .addMinimum(5.5) .build() ).build(); - assertEquals(expected, test); + Assert.assertEquals(expected, test); } @Test @@ -90,7 +90,7 @@ public void testSimpleArraySchema(){ .build() ).build(); - assertEquals(expected, test); + Assert.assertEquals(expected, test); } @Test @@ -107,8 +107,7 @@ public void testSimpleObjectSchema() { .addProperty("name", new StringSchema.Builder().build()) .build() ).build(); - - assertEquals(expected,test); + Assert.assertEquals(expected, test); } @Test @@ -134,7 +133,7 @@ public void testSimpleObjectSchemaWithRequired() { .build() ).build(); - assertEquals(expected,test); + Assert.assertEquals(expected, test); } @Test @@ -165,7 +164,7 @@ public void testSemanticObject(){ .build() ).build(); - assertEquals(expected,test); + Assert.assertEquals(expected, test); } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index b825c62d..dff22b25 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -1,7 +1,193 @@ package ch.unisg.ics.interactions.wot.td.io.json; -import junit.framework.TestCase; +import ch.unisg.ics.interactions.wot.td.ThingDescription; +import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; +import ch.unisg.ics.interactions.wot.td.affordances.Form; +import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; +import ch.unisg.ics.interactions.wot.td.schemas.ObjectSchema; +import ch.unisg.ics.interactions.wot.td.schemas.StringSchema; +import ch.unisg.ics.interactions.wot.td.security.NoSecurityScheme; +import org.junit.Assert; +import org.junit.Test; -public class TDJsonWriterTest extends TestCase { +import javax.json.Json; +import javax.json.JsonObject; +import java.util.ArrayList; +import java.util.List; + +public class TDJsonWriterTest { + + private static final String THING_TITLE = "My Thing"; + private static final String THING_IRI = "http://example.org/#thing"; + private static final String IO_BASE_IRI = "http://example.org/"; + + @Test + public void testEmptyThing() { + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSecurityScheme(new NoSecurityScheme()) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + } + + @Test + public void testThingWithIRI() { + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSecurityScheme(new NoSecurityScheme()) + .addThingURI(THING_IRI) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("title", THING_TITLE) + .add("id", THING_IRI) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + + Assert.assertEquals(expected, test); + } + + @Test + public void testThingWithBase() { + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSecurityScheme(new NoSecurityScheme()) + .addBaseURI(IO_BASE_IRI) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("title", THING_TITLE) + .add("base", IO_BASE_IRI) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + + Assert.assertEquals(expected, test); + } + + @Test + public void testThingWithProperties(){ + List properties = new ArrayList<>(); + properties.add(new PropertyAffordance.Builder( + new StringSchema.Builder().build(), + new Form.Builder(THING_IRI+"/status") + .setMethodName("GET") + .addOperationType("readProperty").build() + ).addObserve().addTitle("status").build()); + + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addBaseURI(IO_BASE_IRI) + .addSecurityScheme(new NoSecurityScheme()) + .addProperties(properties) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .add("base", IO_BASE_IRI) + .add("properties", Json.createObjectBuilder().add("status", + Json.createObjectBuilder() + .add("type", "string") + .add("observable", true) + .add("forms", Json.createArrayBuilder().add( + Json.createObjectBuilder() + .add("href",THING_IRI+"/status") + .add("htv:methodName", "GET") + .add("contentType", "application/json") + .add("op", Json.createArrayBuilder().add("readProperty")) + ) + ) + ) + ) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + + Assert.assertEquals(expected, test); + } + + + @Test + public void testThingWithActions(){ + List actions = new ArrayList<>(); + actions.add(new ActionAffordance.Builder( + new Form.Builder(THING_IRI+"/changeColor") + .setMethodName("POST").build() + ).addTitle("changeColor") + .addInputSchema(new ObjectSchema.Builder() + .addProperty("color", new StringSchema.Builder().build()) + .build()) + .addOutputSchema(new ObjectSchema.Builder() + .addProperty("color", new StringSchema.Builder().build()) + .build()) + .build()); + actions.add(new ActionAffordance.Builder( + new Form.Builder(THING_IRI+"/changeState") + .setMethodName("POST").build() + ).addTitle("changeState").build() + ); + + + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addBaseURI(IO_BASE_IRI) + .addSecurityScheme(new NoSecurityScheme()) + .addActions(actions) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .add("base", IO_BASE_IRI) + .add("actions", Json.createObjectBuilder().add("changeColor", Json.createObjectBuilder() + .add("input", Json.createObjectBuilder() + .add("type", "object").add("properties", Json.createObjectBuilder() + .add("color", Json.createObjectBuilder().add("type", "string"))) + ).add("output", Json.createObjectBuilder() + .add("type", "object").add("properties", Json.createObjectBuilder() + .add("color", Json.createObjectBuilder().add("type", "string"))) + ).add("forms", Json.createArrayBuilder().add( + Json.createObjectBuilder() + .add("href",THING_IRI+"/changeColor") + .add("htv:methodName", "POST") + .add("contentType", "application/json") + .add("op", Json.createArrayBuilder().add("https://www.w3.org/2019/wot/td#invokeAction")) + ) + ) + ).add("changeState", Json.createObjectBuilder() + .add("forms", Json.createArrayBuilder().add( + Json.createObjectBuilder() + .add("href",THING_IRI+"/changeState") + .add("htv:methodName", "POST") + .add("contentType", "application/json") + .add("op", Json.createArrayBuilder().add("https://www.w3.org/2019/wot/td#invokeAction")) + ) + ) + ) + ) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + } } From 3f072b92def5b2009c744b4a67fb741577ef4430 Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 14:17:29 +0200 Subject: [PATCH 08/28] Complete tests for TDJsonWriter --- .../wot/td/io/json/TDJsonWriter.java | 4 +++- .../wot/td/io/json/TDJsonWriterTest.java | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 8252e155..6307d5ec 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -72,8 +72,10 @@ public TDJsonWriter setNamespace(String prefix, String namespace) { @Override protected TDJsonWriter addTypes() { //TODO This is ugly why is the types sometimes a set and sometimes a list? - if(td.getSemanticTypes().size() > 0) { + if(td.getSemanticTypes().size() > 1) { document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); + } else { + document.add(JWot.SEMANTIC_TYPE, td.getSemanticTypes().stream().findFirst().orElse("")); } return this; } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index dff22b25..4fc96134 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -38,6 +38,25 @@ public void testEmptyThing() { Assert.assertEquals(expected, test); } + @Test + public void testThingWithSemanticType() { + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSemanticType("http://w3id.org/eve#Artifact") + .addSecurityScheme(new NoSecurityScheme()) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("@type", "http://w3id.org/eve#Artifact") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + } + @Test public void testThingWithIRI() { ThingDescription td = new ThingDescription.Builder(THING_TITLE) From d7c43a9eeb236d98860696f74d15243123967ff4 Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 14:24:18 +0200 Subject: [PATCH 09/28] Fixing typo and last bug --- .../ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java | 2 +- .../ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 6307d5ec..6fb908b9 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -74,7 +74,7 @@ protected TDJsonWriter addTypes() { //TODO This is ugly why is the types sometimes a set and sometimes a list? if(td.getSemanticTypes().size() > 1) { document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); - } else { + } else if(td.getSemanticTypes().size() > 0){ document.add(JWot.SEMANTIC_TYPE, td.getSemanticTypes().stream().findFirst().orElse("")); } return this; diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java index b7dfa078..fbf0e3eb 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java @@ -119,7 +119,7 @@ public void testSimpleObjectSchemaWithRequired() { .add("type", "string") ).add("age", Json.createObjectBuilder() .add("type", "integer") - .add("miniumum", 0) + .add("minimum", 0) ) ).add("required", Json.createArrayBuilder() .add("name").add("age") From 789f2186ed8413fd82e588f9ea8601529b9caebe Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 14:39:39 +0200 Subject: [PATCH 10/28] Fixing code style issues --- .../ics/interactions/wot/td/io/json/JWot.java | 48 +++++++++---------- .../wot/td/io/json/SchemaJsonReader.java | 4 -- .../wot/td/io/json/SchemaJsonWriter.java | 7 ++- .../wot/td/io/json/TDJsonReader.java | 5 -- .../wot/td/io/json/TDJsonWriter.java | 10 ++-- 5 files changed, 35 insertions(+), 39 deletions(-) delete mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java delete mode 100644 src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java index c1cd59df..0db72812 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java @@ -1,38 +1,38 @@ package ch.unisg.ics.interactions.wot.td.io.json; -public interface JWot { +public final class JWot { - String WOT_CONTEXT = "https://www.w3.org/2019/wot/td/v1"; + public static final String WOT_CONTEXT = "https://www.w3.org/2019/wot/td/v1"; - String BASE = "base"; - String CONTEXT = "@context"; + public static final String BASE = "base"; + public static final String CONTEXT = "@context"; - String TITLE = "title"; - String DESCRIPTION = "description"; + public static final String TITLE = "title"; + public static final String DESCRIPTION = "description"; - String PROPERTIES = "properties"; - String OBSERVABLE = "observable"; + public static final String PROPERTIES = "properties"; + public static final String OBSERVABLE = "observable"; - String ACTIONS = "actions"; - String INPUT = "input"; - String OUTPUT = "output"; - String SAFE = "safe"; - String IDEMPOTENT = "idempotent"; + public static final String ACTIONS = "actions"; + public static final String INPUT = "input"; + public static final String OUTPUT = "output"; + public static final String SAFE = "safe"; + public static final String IDEMPOTENT = "idempotent"; - String EVENTS = "events"; + public static final String EVENTS = "events"; - String SEMANTIC_TYPE = "@type"; - String TYPE = "type"; + public static final String SEMANTIC_TYPE = "@type"; + public static final String TYPE = "type"; - String FORMS = "forms"; - String TARGET = "href"; - String METHOD = "htv:methodName"; - String CONTENT_TYPE = "contentType"; - String SUBPROTOCOL = "subprotocol"; - String OPERATIONS = "op"; + public static final String FORMS = "forms"; + public static final String TARGET = "href"; + public static final String METHOD = "htv:methodName"; + public static final String CONTENT_TYPE = "contentType"; + public static final String SUBPROTOCOL = "subprotocol"; + public static final String OPERATIONS = "op"; - String SECURITY = "security"; - String SECURITY_DEF = "securityDefinitions"; + public static final String SECURITY = "security"; + public static final String SECURITY_DEF = "securityDefinitions"; } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java deleted file mode 100644 index 410eff3b..00000000 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonReader.java +++ /dev/null @@ -1,4 +0,0 @@ -package ch.unisg.ics.interactions.wot.td.io.json; - -public class SchemaJsonReader { -} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java index a7243c37..9a7cf191 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java @@ -6,8 +6,13 @@ import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; -public class SchemaJsonWriter { +public final class SchemaJsonWriter { + /*** + * This methods returns the correct JsonObjectBuilder Object that is generate from the DataSchema. + * @param schema a DataSchema object to convert. + * @return The JsonObjectBuilder that when built will result in a JsonObject representation of the schema. + */ public static JsonObjectBuilder getDataSchema(DataSchema schema) { switch (schema.getDatatype()) { case DataSchema.OBJECT: diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java deleted file mode 100644 index a6352350..00000000 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonReader.java +++ /dev/null @@ -1,5 +0,0 @@ -package ch.unisg.ics.interactions.wot.td.io.json; - -public class TDJsonReader { - -} diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 6fb908b9..b6bd543c 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -74,7 +74,7 @@ protected TDJsonWriter addTypes() { //TODO This is ugly why is the types sometimes a set and sometimes a list? if(td.getSemanticTypes().size() > 1) { document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); - } else if(td.getSemanticTypes().size() > 0){ + } else if(!td.getSemanticTypes().isEmpty()){ document.add(JWot.SEMANTIC_TYPE, td.getSemanticTypes().stream().findFirst().orElse("")); } return this; @@ -109,7 +109,7 @@ protected TDJsonWriter addBaseURI() { @Override protected TDJsonWriter addProperties() { - if(td.getProperties().size() > 0){ + if(!td.getProperties().isEmpty()){ document.add(JWot.PROPERTIES, this.getAffordancesObject(td.getProperties(), this::getProperty)); } return this; @@ -117,7 +117,7 @@ protected TDJsonWriter addProperties() { @Override protected TDJsonWriter addActions() { - if(td.getActions().size() > 0) { + if(!td.getActions().isEmpty()) { document.add(JWot.ACTIONS, this.getAffordancesObject(td.getActions(), this::getAction)); } return this; @@ -133,7 +133,7 @@ protected TDJsonWriter addGraph() { } private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { - if (affordances.size() > 0) { + if (!affordances.isEmpty()) { JsonObjectBuilder rootObj = Json.createObjectBuilder(); affordances.forEach(aff -> rootObj.add(aff.getTitle().get(), mapper.apply(aff)) @@ -177,7 +177,7 @@ private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { JsonObjectBuilder affordanceObj = Json.createObjectBuilder(); //add semantic type(s) - if(affordance.getSemanticTypes().size() > 0) { + if(!affordance.getSemanticTypes().isEmpty()) { affordanceObj.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(affordance.getSemanticTypes())); } From bd85fd5ceaa4c0be31d1f272219f5dcd73ec52cd Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 14:46:01 +0200 Subject: [PATCH 11/28] Fixing utility class declaration --- .../ics/interactions/wot/td/io/json/SchemaJsonWriter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java index 9a7cf191..3a754483 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java @@ -8,6 +8,10 @@ public final class SchemaJsonWriter { + private SchemaJsonWriter(){ + throw new AssertionError(); + } + /*** * This methods returns the correct JsonObjectBuilder Object that is generate from the DataSchema. * @param schema a DataSchema object to convert. From 718293e9e4ffe6d59f2a5903e54316d284b7c17b Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 15:24:07 +0200 Subject: [PATCH 12/28] Increasing test coverage --- .../interactions/wot/td/io/TDWriterTest.java | 45 ++++++++++++++++++ .../wot/td/io/graph/TDGraphWriterTest.java | 46 +++++++++---------- .../wot/td/io/json/TDJsonWriterTest.java | 41 +++++++++++++++++ 3 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/test/java/ch/unisg/ics/interactions/wot/td/io/TDWriterTest.java diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/TDWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/TDWriterTest.java new file mode 100644 index 00000000..6b2fc98a --- /dev/null +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/TDWriterTest.java @@ -0,0 +1,45 @@ +package ch.unisg.ics.interactions.wot.td.io; + + +import ch.unisg.ics.interactions.wot.td.ThingDescription; +import ch.unisg.ics.interactions.wot.td.security.NoSecurityScheme; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class TDWriterTest { + + private static final String THING_TITLE = "My Thing"; + private static final String PREFIXES = + "@prefix td: .\n" + + "@prefix htv: .\n" + + "@prefix hctl: .\n" + + "@prefix dct: .\n" + + "@prefix wotsec: .\n" + + "@prefix js: .\n" + + "@prefix saref: .\n" + + "@prefix xsd: .\n"; + private static final String IO_BASE_IRI = "http://example.org/"; + + ThingDescription td; + + @Before + public void init(){ + this.td = new ThingDescription.Builder(THING_TITLE) + .addSecurityScheme(new NoSecurityScheme()) + .build(); + } + + @Test + //TODO change this as soon as you have the reader implemented + public void testWriteJSON() { + String jsonTD = "{\"@context\":\"https://www.w3.org/2019/wot/td/v1\",\"title\":\"My Thing\",\"securityDefinitions\":{\"nosec_sc\":{\"scheme\":\"nosec\"}},\"security\":[\"nosec_sc\"]}"; + Assert.assertEquals(jsonTD, TDWriter.write(td, RDFFormat.JSONLD)); + } + + @Test + public void testWriteTurtle(){ + //TODO implement + } +} diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriterTest.java index 5eaf9a4d..65bd78bd 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphWriterTest.java @@ -1,14 +1,16 @@ package ch.unisg.ics.interactions.wot.td.io.graph; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import ch.unisg.ics.interactions.wot.td.io.graph.ReadWriteUtils; -import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphWriter; +import ch.unisg.ics.interactions.wot.td.ThingDescription; +import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; +import ch.unisg.ics.interactions.wot.td.affordances.Form; +import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; +import ch.unisg.ics.interactions.wot.td.schemas.BooleanSchema; +import ch.unisg.ics.interactions.wot.td.schemas.IntegerSchema; +import ch.unisg.ics.interactions.wot.td.schemas.NumberSchema; +import ch.unisg.ics.interactions.wot.td.schemas.ObjectSchema; +import ch.unisg.ics.interactions.wot.td.security.APIKeySecurityScheme; +import ch.unisg.ics.interactions.wot.td.security.APIKeySecurityScheme.TokenLocation; +import ch.unisg.ics.interactions.wot.td.security.NoSecurityScheme; import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.ValueFactory; @@ -23,17 +25,13 @@ import org.eclipse.rdf4j.rio.RDFParseException; import org.junit.Test; -import ch.unisg.ics.interactions.wot.td.ThingDescription; -import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; -import ch.unisg.ics.interactions.wot.td.affordances.Form; -import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; -import ch.unisg.ics.interactions.wot.td.schemas.BooleanSchema; -import ch.unisg.ics.interactions.wot.td.schemas.IntegerSchema; -import ch.unisg.ics.interactions.wot.td.schemas.NumberSchema; -import ch.unisg.ics.interactions.wot.td.schemas.ObjectSchema; -import ch.unisg.ics.interactions.wot.td.security.APIKeySecurityScheme; -import ch.unisg.ics.interactions.wot.td.security.APIKeySecurityScheme.TokenLocation; -import ch.unisg.ics.interactions.wot.td.security.NoSecurityScheme; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertTrue; public class TDGraphWriterTest { private static final String THING_TITLE = "My Thing"; @@ -223,8 +221,8 @@ public void testWritePropertySubprotocol() throws RDFParseException, RDFHandlerE .addObserve() .build(); - ThingDescription td = constructThingDescription(new ArrayList(Arrays.asList(property)), - new ArrayList(Arrays.asList())); + ThingDescription td = constructThingDescription(new ArrayList<>(Collections.singletonList(property)), + new ArrayList<>(Collections.emptyList())); assertIsomorphicGraphs(testTD, td); } @@ -283,8 +281,8 @@ public void testWriteOneAction() throws RDFParseException, RDFHandlerException, .build()) .build(); - ThingDescription td = constructThingDescription(new ArrayList(), - new ArrayList(Arrays.asList(simpleAction))); + ThingDescription td = constructThingDescription(new ArrayList<>(), + new ArrayList<>(Collections.singletonList(simpleAction))); assertIsomorphicGraphs(testTD, td); } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 4fc96134..8d6e5e35 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -57,6 +57,47 @@ public void testThingWithSemanticType() { Assert.assertEquals(expected, test); } + @Test + public void testThingWithSemanticTypes() { + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSemanticType("http://w3id.org/eve#Artifact") + .addSemanticType("http://w3id.org/td#Thing") + .addSecurityScheme(new NoSecurityScheme()) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("@type", Json.createArrayBuilder().add("http://w3id.org/eve#Artifact").add("http://w3id.org/td#Thing")) + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + } + + @Test + public void testThingWithNameSpace(){ + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSemanticType("http://w3id.org/eve#Artifact") + .addSecurityScheme(new NoSecurityScheme()) + .build(); + + JsonObject test = new TDJsonWriter(td).setNamespace("ex", "https://example.org/#").getJson(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", Json.createArrayBuilder().add("https://www.w3.org/2019/wot/td/v1") + .add(Json.createObjectBuilder().add("ex", "https://example.org/#")) + ).add("@type", "http://w3id.org/eve#Artifact") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + Assert.assertEquals(expected, test); + } + @Test public void testThingWithIRI() { ThingDescription td = new ThingDescription.Builder(THING_TITLE) From e16e28273d97b6945a665d9ac55e7577ded6d88d Mon Sep 17 00:00:00 2001 From: Samuele Date: Fri, 17 Sep 2021 17:39:56 +0200 Subject: [PATCH 13/28] Now use prefixes in the right way --- .../wot/td/io/json/TDJsonWriter.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index b6bd543c..a92f5ab7 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -10,9 +10,7 @@ import javax.json.*; import java.io.ByteArrayOutputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.function.Function; /** @@ -22,11 +20,13 @@ public class TDJsonWriter extends AbstractTDWriter { private final JsonObjectBuilder document; private Optional semanticContext; + private final Map prefixMap; public TDJsonWriter(ThingDescription td) { super(td); document = Json.createObjectBuilder(); semanticContext = Optional.empty(); + prefixMap = new HashMap<>(); } public JsonObject getJson(){ @@ -58,6 +58,7 @@ public String write() { @Override public TDJsonWriter setNamespace(String prefix, String namespace) { + this.prefixMap.put(namespace, prefix); if(semanticContext.isPresent()){ semanticContext.get().add(prefix, namespace); } else { @@ -127,11 +128,27 @@ protected TDJsonWriter addActions() { protected TDJsonWriter addGraph() { td.getGraph().ifPresent(g -> g.getStatements(null, null, null).forEach(statement -> { //TODO I'm not sure this is the right way to parse the statement - document.add(statement.getPredicate().stringValue(), statement.getObject().stringValue()); + document.add(getPrefixedAnnotation(statement.getPredicate().stringValue()),statement.getObject().stringValue()); })); return this; } + private String getPrefixedAnnotation(String annotation){ + String[] splitAnnotation = annotation.split("#"); + if(splitAnnotation.length <= 1){ + return annotation; + } + String root = splitAnnotation[0]+'#'; + String fragment = splitAnnotation[1]; + String result; + if(this.prefixMap.containsKey(root)) { + result = this.prefixMap.get(root)+":"+fragment; + } else { + result = annotation; + } + return result; + } + private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { if (!affordances.isEmpty()) { JsonObjectBuilder rootObj = Json.createObjectBuilder(); @@ -169,7 +186,7 @@ private JsonObjectBuilder getAction(ActionAffordance action) { private JsonArrayBuilder getSemanticTypes(List semanticTypes) { JsonArrayBuilder types = Json.createArrayBuilder(); - semanticTypes.forEach(types::add); + semanticTypes.forEach(t -> types.add(getPrefixedAnnotation(t))); return types; } From e8a02a2ee0776bbb3ffe3ffda63421495f9f19f8 Mon Sep 17 00:00:00 2001 From: Samuele Date: Mon, 20 Sep 2021 11:03:07 +0200 Subject: [PATCH 14/28] Change required field from title to name in TDs affordances --- .../interactions/wot/td/io/json/TDJsonWriter.java | 12 ++++++++---- .../wot/td/io/json/TDJsonWriterTest.java | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index a92f5ab7..a7d2c79a 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -11,6 +11,7 @@ import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; /** @@ -152,8 +153,12 @@ private String getPrefixedAnnotation(String annotation){ private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { if (!affordances.isEmpty()) { JsonObjectBuilder rootObj = Json.createObjectBuilder(); - affordances.forEach(aff -> - rootObj.add(aff.getTitle().get(), mapper.apply(aff)) + //TODO check this because it doesn't look good + AtomicInteger index = new AtomicInteger(); + affordances.forEach(aff ->{ + index.addAndGet(1); + rootObj.add(aff.getName().get(), mapper.apply(aff)); + } ); return rootObj; } @@ -198,8 +203,7 @@ private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { affordanceObj.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(affordance.getSemanticTypes())); } - //add readable name - affordance.getName().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); + affordance.getTitle().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); //TODO description is missing in the model diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 8d6e5e35..4ddb6e28 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -148,7 +148,7 @@ public void testThingWithProperties(){ new Form.Builder(THING_IRI+"/status") .setMethodName("GET") .addOperationType("readProperty").build() - ).addObserve().addTitle("status").build()); + ).addObserve().addName("status").build()); ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addBaseURI(IO_BASE_IRI) @@ -191,7 +191,7 @@ public void testThingWithActions(){ actions.add(new ActionAffordance.Builder( new Form.Builder(THING_IRI+"/changeColor") .setMethodName("POST").build() - ).addTitle("changeColor") + ).addName("changeColor") .addInputSchema(new ObjectSchema.Builder() .addProperty("color", new StringSchema.Builder().build()) .build()) @@ -202,7 +202,7 @@ public void testThingWithActions(){ actions.add(new ActionAffordance.Builder( new Form.Builder(THING_IRI+"/changeState") .setMethodName("POST").build() - ).addTitle("changeState").build() + ).addName("changeState").build() ); From 0cf11c52cdad1dbcb5d02566e58e3c414b410a00 Mon Sep 17 00:00:00 2001 From: Samuele Date: Mon, 20 Sep 2021 11:03:37 +0200 Subject: [PATCH 15/28] Remove unused code --- .../ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index a7d2c79a..7a9b0547 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -153,10 +153,7 @@ private String getPrefixedAnnotation(String annotation){ private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { if (!affordances.isEmpty()) { JsonObjectBuilder rootObj = Json.createObjectBuilder(); - //TODO check this because it doesn't look good - AtomicInteger index = new AtomicInteger(); affordances.forEach(aff ->{ - index.addAndGet(1); rootObj.add(aff.getName().get(), mapper.apply(aff)); } ); From 7e217f807bbcd580e41f3cd4385787b01f14ba53 Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 11:29:55 +0200 Subject: [PATCH 16/28] Fix json operation types --- .../ics/interactions/wot/td/io/json/JWot.java | 15 +++++++++++++++ .../interactions/wot/td/io/json/TDJsonWriter.java | 9 ++++++++- .../wot/td/io/json/TDJsonWriterTest.java | 15 +++++++++++---- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java index 0db72812..910be39a 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java @@ -1,5 +1,13 @@ package ch.unisg.ics.interactions.wot.td.io.json; +import ch.unisg.ics.interactions.wot.td.vocabularies.TD; + +import javax.json.Json; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + public final class JWot { public static final String WOT_CONTEXT = "https://www.w3.org/2019/wot/td/v1"; @@ -35,4 +43,11 @@ public final class JWot { public static final String SECURITY = "security"; public static final String SECURITY_DEF = "securityDefinitions"; + public static final Map JSON_OPERATION_TYPES = + Arrays.stream(new String[][] { + {TD.readProperty, "readproperty" }, + {TD.writeProperty, "writeproperty" }, + {TD.invokeAction, "invokeaction" }, + }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); + } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index a92f5ab7..710ae44e 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -220,7 +220,14 @@ private JsonArrayBuilder getFormsArray(List forms) { //Add operations JsonArrayBuilder opArray = Json.createArrayBuilder(); - form.getOperationTypes().forEach(opArray::add); + form.getOperationTypes().forEach(op -> { + if (JWot.JSON_OPERATION_TYPES.containsKey(op)) { + opArray.add((String) JWot.JSON_OPERATION_TYPES.get(op)); + } + else { + opArray.add(op); + } + }); formObj.add(JWot.OPERATIONS, opArray); //Add methodName only if there is one operation type to avoid ambiguity diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 8d6e5e35..9ea65875 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -4,14 +4,19 @@ import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; import ch.unisg.ics.interactions.wot.td.affordances.Form; import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; +import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphWriter; import ch.unisg.ics.interactions.wot.td.schemas.ObjectSchema; import ch.unisg.ics.interactions.wot.td.schemas.StringSchema; import ch.unisg.ics.interactions.wot.td.security.NoSecurityScheme; +import ch.unisg.ics.interactions.wot.td.vocabularies.TD; import org.junit.Assert; import org.junit.Test; import javax.json.Json; import javax.json.JsonObject; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -147,7 +152,7 @@ public void testThingWithProperties(){ new StringSchema.Builder().build(), new Form.Builder(THING_IRI+"/status") .setMethodName("GET") - .addOperationType("readProperty").build() + .addOperationType(TD.readProperty).build() ).addObserve().addTitle("status").build()); ThingDescription td = new ThingDescription.Builder(THING_TITLE) @@ -171,7 +176,7 @@ public void testThingWithProperties(){ .add("href",THING_IRI+"/status") .add("htv:methodName", "GET") .add("contentType", "application/json") - .add("op", Json.createArrayBuilder().add("readProperty")) + .add("op", Json.createArrayBuilder().add("readproperty")) ) ) ) @@ -179,6 +184,8 @@ public void testThingWithProperties(){ .build(); JsonObject test = new TDJsonWriter(td).getJson(); + + System.out.println(test); Assert.assertEquals(expected, test); Assert.assertEquals(expected, test); @@ -230,7 +237,7 @@ public void testThingWithActions(){ .add("href",THING_IRI+"/changeColor") .add("htv:methodName", "POST") .add("contentType", "application/json") - .add("op", Json.createArrayBuilder().add("https://www.w3.org/2019/wot/td#invokeAction")) + .add("op", Json.createArrayBuilder().add("invokeaction")) ) ) ).add("changeState", Json.createObjectBuilder() @@ -239,7 +246,7 @@ public void testThingWithActions(){ .add("href",THING_IRI+"/changeState") .add("htv:methodName", "POST") .add("contentType", "application/json") - .add("op", Json.createArrayBuilder().add("https://www.w3.org/2019/wot/td#invokeAction")) + .add("op", Json.createArrayBuilder().add("invokeaction")) ) ) ) From 653f73cc4cc61d62ebb6385452111bd8783c682f Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 11:32:16 +0200 Subject: [PATCH 17/28] Remove duplicate line --- .../unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 9ea65875..1ac23c92 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -120,8 +120,6 @@ public void testThingWithIRI() { JsonObject test = new TDJsonWriter(td).getJson(); Assert.assertEquals(expected, test); - - Assert.assertEquals(expected, test); } @Test From 77bfe58d5efbf74508402911575763bd73bbe8e1 Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 14:15:22 +0200 Subject: [PATCH 18/28] Enable sem annotation without fragments --- .../wot/td/io/json/TDJsonWriter.java | 31 ++++++---- .../wot/td/io/json/TDJsonWriterTest.java | 60 +++++++++++++------ 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 710ae44e..afe4e4f0 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -12,6 +12,9 @@ import java.io.OutputStream; import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.Comparator.*; /** * A writer to serialize TDs in the JSON-LD 1.1 format. @@ -73,10 +76,12 @@ public TDJsonWriter setNamespace(String prefix, String namespace) { @Override protected TDJsonWriter addTypes() { //TODO This is ugly why is the types sometimes a set and sometimes a list? + if(td.getSemanticTypes().size() > 1) { document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); } else if(!td.getSemanticTypes().isEmpty()){ - document.add(JWot.SEMANTIC_TYPE, td.getSemanticTypes().stream().findFirst().orElse("")); + document.add(JWot.SEMANTIC_TYPE, + this.getPrefixedAnnotation(td.getSemanticTypes().stream().findFirst().orElse(""))); } return this; } @@ -134,19 +139,23 @@ protected TDJsonWriter addGraph() { } private String getPrefixedAnnotation(String annotation){ - String[] splitAnnotation = annotation.split("#"); - if(splitAnnotation.length <= 1){ + Map matchedPref= prefixMap.entrySet() + .stream() + .filter(map -> annotation.startsWith(map.getKey())) + .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + + if (matchedPref.isEmpty()) { return annotation; } - String root = splitAnnotation[0]+'#'; - String fragment = splitAnnotation[1]; - String result; - if(this.prefixMap.containsKey(root)) { - result = this.prefixMap.get(root)+":"+fragment; - } else { - result = annotation; + if (matchedPref.size() == 1) { + String namespace = (String) matchedPref.keySet().toArray()[0]; + return annotation.replace(namespace, matchedPref.get(namespace) + ":"); + } + else { + Map.Entry bestMatch = Collections.max(matchedPref.entrySet(), + comparing(Map.Entry::getKey)); + return annotation.replace(bestMatch.getKey(), bestMatch.getValue() + ":"); } - return result; } private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 1ac23c92..75230a27 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -4,7 +4,6 @@ import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; import ch.unisg.ics.interactions.wot.td.affordances.Form; import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; -import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphWriter; import ch.unisg.ics.interactions.wot.td.schemas.ObjectSchema; import ch.unisg.ics.interactions.wot.td.schemas.StringSchema; import ch.unisg.ics.interactions.wot.td.security.NoSecurityScheme; @@ -14,9 +13,6 @@ import javax.json.Json; import javax.json.JsonObject; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -66,19 +62,27 @@ public void testThingWithSemanticType() { public void testThingWithSemanticTypes() { ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addSemanticType("http://w3id.org/eve#Artifact") - .addSemanticType("http://w3id.org/td#Thing") + .addSemanticType("http://iotschema.org/Light") .addSecurityScheme(new NoSecurityScheme()) .build(); JsonObject expected = Json.createObjectBuilder() - .add("@context", "https://www.w3.org/2019/wot/td/v1") - .add("@type", Json.createArrayBuilder().add("http://w3id.org/eve#Artifact").add("http://w3id.org/td#Thing")) + .add("@context", Json.createArrayBuilder() + .add("https://www.w3.org/2019/wot/td/v1") + .add(Json.createObjectBuilder() + .add("eve", "http://w3id.org/eve#") + .add("iot", "http://iotschema.org/").build()).build()) + .add("@type", Json.createArrayBuilder().add("eve:Artifact").add("iot:Light")) .add("title", THING_TITLE) .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) .add("security", Json.createArrayBuilder().add("nosec_sc")) .build(); - JsonObject test = new TDJsonWriter(td).getJson(); + JsonObject test = new TDJsonWriter(td) + .setNamespace("eve", "http://w3id.org/eve#") + .setNamespace("iot", "http://iotschema.org/") + .getJson(); + Assert.assertEquals(expected, test); } @@ -89,12 +93,40 @@ public void testThingWithNameSpace(){ .addSecurityScheme(new NoSecurityScheme()) .build(); - JsonObject test = new TDJsonWriter(td).setNamespace("ex", "https://example.org/#").getJson(); + JsonObject test = new TDJsonWriter(td).setNamespace("eve", "http://w3id.org/eve#").getJson(); JsonObject expected = Json.createObjectBuilder() .add("@context", Json.createArrayBuilder().add("https://www.w3.org/2019/wot/td/v1") - .add(Json.createObjectBuilder().add("ex", "https://example.org/#")) - ).add("@type", "http://w3id.org/eve#Artifact") + .add(Json.createObjectBuilder().add("eve", "http://w3id.org/eve#")) + ).add("@type", "eve:Artifact") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + Assert.assertEquals(expected, test); + } + + @Test + public void testThingWithOverlappingNameSpaces(){ + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSemanticType("http://example.org/Type1") + .addSemanticType("http://example.org/overlapping/Type2") + .addSecurityScheme(new NoSecurityScheme()) + .build(); + + JsonObject test = new TDJsonWriter(td) + .setNamespace("ex1", "http://example.org/") + .setNamespace("ex2", "http://example.org/overlapping/") + .getJson(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", Json.createArrayBuilder() + .add("https://www.w3.org/2019/wot/td/v1") + .add(Json.createObjectBuilder() + .add("ex1", "http://example.org/") + .add("ex2", "http://example.org/overlapping/"))) + .add("@type", Json.createArrayBuilder().add("ex1:Type1").add("ex2:Type2")) .add("title", THING_TITLE) .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) .add("security", Json.createArrayBuilder().add("nosec_sc")) @@ -139,8 +171,6 @@ public void testThingWithBase() { JsonObject test = new TDJsonWriter(td).getJson(); Assert.assertEquals(expected, test); - - Assert.assertEquals(expected, test); } @Test @@ -182,10 +212,6 @@ public void testThingWithProperties(){ .build(); JsonObject test = new TDJsonWriter(td).getJson(); - - System.out.println(test); - Assert.assertEquals(expected, test); - Assert.assertEquals(expected, test); } From d9b1dac80b54cd94b98a9c2ae91eb1d942f4b39f Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 14:39:22 +0200 Subject: [PATCH 19/28] Enable affordance @type as stream --- .../wot/td/io/json/TDJsonWriter.java | 46 +++--- .../wot/td/io/json/TDJsonWriterTest.java | 150 ++++++++++++++++-- 2 files changed, 157 insertions(+), 39 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index afe4e4f0..0eb04858 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -14,7 +14,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static java.util.Comparator.*; +import static java.util.Comparator.comparing; /** * A writer to serialize TDs in the JSON-LD 1.1 format. @@ -22,8 +22,8 @@ public class TDJsonWriter extends AbstractTDWriter { private final JsonObjectBuilder document; - private Optional semanticContext; private final Map prefixMap; + private Optional semanticContext; public TDJsonWriter(ThingDescription td) { super(td); @@ -32,8 +32,8 @@ public TDJsonWriter(ThingDescription td) { prefixMap = new HashMap<>(); } - public JsonObject getJson(){ - if(semanticContext.isPresent()){ + public JsonObject getJson() { + if (semanticContext.isPresent()) { document.add(JWot.CONTEXT, Json.createArrayBuilder() .add(JWot.WOT_CONTEXT) .add(semanticContext.get())); @@ -62,11 +62,11 @@ public String write() { @Override public TDJsonWriter setNamespace(String prefix, String namespace) { this.prefixMap.put(namespace, prefix); - if(semanticContext.isPresent()){ + if (semanticContext.isPresent()) { semanticContext.get().add(prefix, namespace); } else { JsonObjectBuilder semContextObj = Json.createObjectBuilder() - .add(prefix, namespace); + .add(prefix, namespace); semanticContext = Optional.of(semContextObj); } @@ -77,9 +77,9 @@ public TDJsonWriter setNamespace(String prefix, String namespace) { protected TDJsonWriter addTypes() { //TODO This is ugly why is the types sometimes a set and sometimes a list? - if(td.getSemanticTypes().size() > 1) { + if (td.getSemanticTypes().size() > 1) { document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); - } else if(!td.getSemanticTypes().isEmpty()){ + } else if (!td.getSemanticTypes().isEmpty()) { document.add(JWot.SEMANTIC_TYPE, this.getPrefixedAnnotation(td.getSemanticTypes().stream().findFirst().orElse(""))); } @@ -100,7 +100,7 @@ protected TDJsonWriter addSecurity() { //Add security def document.add(JWot.SECURITY_DEF, Json.createObjectBuilder().add("nosec_sc", - Json.createObjectBuilder().add("scheme", "nosec" )) + Json.createObjectBuilder().add("scheme", "nosec")) ); //Add actual security field document.add(JWot.SECURITY, Json.createArrayBuilder().add("nosec_sc")); @@ -115,7 +115,7 @@ protected TDJsonWriter addBaseURI() { @Override protected TDJsonWriter addProperties() { - if(!td.getProperties().isEmpty()){ + if (!td.getProperties().isEmpty()) { document.add(JWot.PROPERTIES, this.getAffordancesObject(td.getProperties(), this::getProperty)); } return this; @@ -123,7 +123,7 @@ protected TDJsonWriter addProperties() { @Override protected TDJsonWriter addActions() { - if(!td.getActions().isEmpty()) { + if (!td.getActions().isEmpty()) { document.add(JWot.ACTIONS, this.getAffordancesObject(td.getActions(), this::getAction)); } return this; @@ -133,13 +133,13 @@ protected TDJsonWriter addActions() { protected TDJsonWriter addGraph() { td.getGraph().ifPresent(g -> g.getStatements(null, null, null).forEach(statement -> { //TODO I'm not sure this is the right way to parse the statement - document.add(getPrefixedAnnotation(statement.getPredicate().stringValue()),statement.getObject().stringValue()); + document.add(getPrefixedAnnotation(statement.getPredicate().stringValue()), statement.getObject().stringValue()); })); return this; } - private String getPrefixedAnnotation(String annotation){ - Map matchedPref= prefixMap.entrySet() + private String getPrefixedAnnotation(String annotation) { + Map matchedPref = prefixMap.entrySet() .stream() .filter(map -> annotation.startsWith(map.getKey())) .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); @@ -150,15 +150,14 @@ private String getPrefixedAnnotation(String annotation){ if (matchedPref.size() == 1) { String namespace = (String) matchedPref.keySet().toArray()[0]; return annotation.replace(namespace, matchedPref.get(namespace) + ":"); - } - else { + } else { Map.Entry bestMatch = Collections.max(matchedPref.entrySet(), comparing(Map.Entry::getKey)); return annotation.replace(bestMatch.getKey(), bestMatch.getValue() + ":"); } } - private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { + private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { if (!affordances.isEmpty()) { JsonObjectBuilder rootObj = Json.createObjectBuilder(); affordances.forEach(aff -> @@ -203,8 +202,12 @@ private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { JsonObjectBuilder affordanceObj = Json.createObjectBuilder(); //add semantic type(s) - if(!affordance.getSemanticTypes().isEmpty()) { - affordanceObj.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(affordance.getSemanticTypes())); + if (affordance.getSemanticTypes().size() > 1) { + affordanceObj.add(JWot.SEMANTIC_TYPE, + this.getSemanticTypes(new ArrayList<>(affordance.getSemanticTypes()))); + } else if (!affordance.getSemanticTypes().isEmpty()) { + affordanceObj.add(JWot.SEMANTIC_TYPE, + this.getPrefixedAnnotation(affordance.getSemanticTypes().stream().findFirst().orElse(""))); } //add readable name @@ -232,8 +235,7 @@ private JsonArrayBuilder getFormsArray(List forms) { form.getOperationTypes().forEach(op -> { if (JWot.JSON_OPERATION_TYPES.containsKey(op)) { opArray.add((String) JWot.JSON_OPERATION_TYPES.get(op)); - } - else { + } else { opArray.add(op); } }); @@ -241,7 +243,7 @@ private JsonArrayBuilder getFormsArray(List forms) { //Add methodName only if there is one operation type to avoid ambiguity form.getMethodName().ifPresent(m -> { - if(form.getOperationTypes().size() == 1) + if (form.getOperationTypes().size() == 1) formObj.add(JWot.METHOD, m); }); formArray.add(formObj); diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 75230a27..08048eb5 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -87,7 +87,7 @@ public void testThingWithSemanticTypes() { } @Test - public void testThingWithNameSpace(){ + public void testThingWithNameSpace() { ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addSemanticType("http://w3id.org/eve#Artifact") .addSecurityScheme(new NoSecurityScheme()) @@ -108,7 +108,7 @@ public void testThingWithNameSpace(){ } @Test - public void testThingWithOverlappingNameSpaces(){ + public void testThingWithOverlappingNameSpaces() { ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addSemanticType("http://example.org/Type1") .addSemanticType("http://example.org/overlapping/Type2") @@ -174,11 +174,11 @@ public void testThingWithBase() { } @Test - public void testThingWithProperties(){ + public void testThingWithProperties() { List properties = new ArrayList<>(); properties.add(new PropertyAffordance.Builder( new StringSchema.Builder().build(), - new Form.Builder(THING_IRI+"/status") + new Form.Builder(THING_IRI + "/status") .setMethodName("GET") .addOperationType(TD.readProperty).build() ).addObserve().addTitle("status").build()); @@ -201,7 +201,7 @@ public void testThingWithProperties(){ .add("observable", true) .add("forms", Json.createArrayBuilder().add( Json.createObjectBuilder() - .add("href",THING_IRI+"/status") + .add("href", THING_IRI + "/status") .add("htv:methodName", "GET") .add("contentType", "application/json") .add("op", Json.createArrayBuilder().add("readproperty")) @@ -215,14 +215,62 @@ public void testThingWithProperties(){ Assert.assertEquals(expected, test); } + @Test + public void testThingPropertiesWithOneSemanticType() { + List properties = new ArrayList<>(); + properties.add(new PropertyAffordance.Builder( + new StringSchema.Builder().build(), + new Form.Builder(THING_IRI + "/status").build()) + .addObserve() + .addTitle("status") + .addSemanticType("http://example.org/Status") + .build()); + + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addBaseURI(IO_BASE_IRI) + .addSecurityScheme(new NoSecurityScheme()) + .addProperties(properties) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", Json.createArrayBuilder() + .add("https://www.w3.org/2019/wot/td/v1") + .add(Json.createObjectBuilder() + .add("ex", "http://example.org/"))) + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .add("base", IO_BASE_IRI) + .add("properties", Json.createObjectBuilder().add("status", + Json.createObjectBuilder() + .add("type", "string") + .add("@type", "ex:Status") + .add("observable", true) + .add("forms", Json.createArrayBuilder().add( + Json.createObjectBuilder() + .add("href", THING_IRI + "/status") + .add("contentType", "application/json") + .add("op", Json.createArrayBuilder().add("readproperty").add("writeproperty")) + ) + ) + ) + ) + .build(); + + JsonObject test = new TDJsonWriter(td) + .setNamespace("ex", "http://example.org/") + .getJson(); + Assert.assertEquals(expected, test); + } + @Test - public void testThingWithActions(){ + public void testThingWithActions() { List actions = new ArrayList<>(); actions.add(new ActionAffordance.Builder( - new Form.Builder(THING_IRI+"/changeColor") - .setMethodName("POST").build() - ).addTitle("changeColor") + new Form.Builder(THING_IRI + "/changeColor") + .setMethodName("POST").build() + ).addTitle("changeColor") .addInputSchema(new ObjectSchema.Builder() .addProperty("color", new StringSchema.Builder().build()) .build()) @@ -231,7 +279,7 @@ public void testThingWithActions(){ .build()) .build()); actions.add(new ActionAffordance.Builder( - new Form.Builder(THING_IRI+"/changeState") + new Form.Builder(THING_IRI + "/changeState") .setMethodName("POST").build() ).addTitle("changeState").build() ); @@ -254,22 +302,87 @@ public void testThingWithActions(){ .add("type", "object").add("properties", Json.createObjectBuilder() .add("color", Json.createObjectBuilder().add("type", "string"))) ).add("output", Json.createObjectBuilder() - .add("type", "object").add("properties", Json.createObjectBuilder() - .add("color", Json.createObjectBuilder().add("type", "string"))) + .add("type", "object").add("properties", Json.createObjectBuilder() + .add("color", Json.createObjectBuilder().add("type", "string"))) ).add("forms", Json.createArrayBuilder().add( + Json.createObjectBuilder() + .add("href", THING_IRI + "/changeColor") + .add("htv:methodName", "POST") + .add("contentType", "application/json") + .add("op", Json.createArrayBuilder().add("invokeaction")) + ) + ) + ).add("changeState", Json.createObjectBuilder() + .add("forms", Json.createArrayBuilder().add( Json.createObjectBuilder() - .add("href",THING_IRI+"/changeColor") + .add("href", THING_IRI + "/changeState") .add("htv:methodName", "POST") .add("contentType", "application/json") .add("op", Json.createArrayBuilder().add("invokeaction")) ) ) - ).add("changeState", Json.createObjectBuilder() + ) + ) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + } + + @Test + public void testThingActionsWithSemanticTypes() { + List actions = new ArrayList<>(); + actions.add(new ActionAffordance.Builder( + new Form.Builder(THING_IRI + "/changeColor") + .build()) + .addTitle("changeColor") + .addSemanticType("http://example.org/1/SetColor1") + .addSemanticType("http://example.org/2/SetColor2") + .build()); + actions.add(new ActionAffordance.Builder( + new Form.Builder(THING_IRI + "/changeState").build()) + .addTitle("changeState") + .addSemanticType("http://example.org/1/SetState1") + .addSemanticType("http://example.org/2/SetState2") + .build()); + + + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addBaseURI(IO_BASE_IRI) + .addSecurityScheme(new NoSecurityScheme()) + .addActions(actions) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", Json.createArrayBuilder() + .add("https://www.w3.org/2019/wot/td/v1") + .add(Json.createObjectBuilder() + .add("ex1", "http://example.org/1/") + .add("ex2", "http://example.org/2/"))) + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .add("base", IO_BASE_IRI) + .add("actions", Json.createObjectBuilder() + .add("changeColor", Json.createObjectBuilder() + .add("@type", Json.createArrayBuilder() + .add("ex1:SetColor1") + .add("ex2:SetColor2")) + .add("forms", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("href", THING_IRI + "/changeColor") + .add("contentType", "application/json") + .add("htv:methodName", "POST") + .add("op", Json.createArrayBuilder().add("invokeaction"))))) + .add("changeState", Json.createObjectBuilder() + .add("@type", Json.createArrayBuilder() + .add("ex1:SetState1") + .add("ex2:SetState2")) .add("forms", Json.createArrayBuilder().add( Json.createObjectBuilder() - .add("href",THING_IRI+"/changeState") - .add("htv:methodName", "POST") + .add("href", THING_IRI + "/changeState") .add("contentType", "application/json") + .add("htv:methodName", "POST") .add("op", Json.createArrayBuilder().add("invokeaction")) ) ) @@ -277,7 +390,10 @@ public void testThingWithActions(){ ) .build(); - JsonObject test = new TDJsonWriter(td).getJson(); + JsonObject test = new TDJsonWriter(td) + .setNamespace("ex1", "http://example.org/1/") + .setNamespace("ex2", "http://example.org/2/") + .getJson(); Assert.assertEquals(expected, test); } From d5d4e8f050392139f463cf0ad840fb0d1adff798 Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 15:09:26 +0200 Subject: [PATCH 20/28] Ignore TD namespace --- .../wot/td/io/json/TDJsonWriter.java | 6 ++++++ .../wot/td/io/json/TDJsonWriterTest.java | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 0eb04858..b5c28577 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -6,6 +6,7 @@ import ch.unisg.ics.interactions.wot.td.affordances.InteractionAffordance; import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; import ch.unisg.ics.interactions.wot.td.io.AbstractTDWriter; +import ch.unisg.ics.interactions.wot.td.vocabularies.TD; import javax.json.*; import java.io.ByteArrayOutputStream; @@ -139,6 +140,11 @@ protected TDJsonWriter addGraph() { } private String getPrefixedAnnotation(String annotation) { + + if (annotation.startsWith(TD.PREFIX)) { + return annotation.replace(TD.PREFIX,""); + } + Map matchedPref = prefixMap.entrySet() .stream() .filter(map -> annotation.startsWith(map.getKey())) diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 08048eb5..14e86bf9 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -39,6 +39,25 @@ public void testEmptyThing() { Assert.assertEquals(expected, test); } + @Test + public void testThingWithTDOntologyPrefix() { + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSecurityScheme(new NoSecurityScheme()) + .addSemanticType(TD.Thing) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", "https://www.w3.org/2019/wot/td/v1") + .add("@type", "Thing") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .build(); + + JsonObject test = new TDJsonWriter(td).getJson(); + Assert.assertEquals(expected, test); + } + @Test public void testThingWithSemanticType() { ThingDescription td = new ThingDescription.Builder(THING_TITLE) From 9d4a86b75747b914c1e84cdb1ee97680c7fb2ea0 Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 16:49:29 +0200 Subject: [PATCH 21/28] Update namespaces on add graph --- .../interactions/wot/td/ThingDescription.java | 180 +++++++++--------- 1 file changed, 92 insertions(+), 88 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/ThingDescription.java b/src/main/java/ch/unisg/ics/interactions/wot/td/ThingDescription.java index cd74363f..2d5377c0 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/ThingDescription.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/ThingDescription.java @@ -19,41 +19,41 @@ import ch.unisg.ics.interactions.wot.td.security.SecurityScheme; /** - * An immutable representation of a W3C Web of - * Things Thing Description (TD). A ThingDescription is instantiated using a + * An immutable representation of a W3C Web of + * Things Thing Description (TD). A ThingDescription is instantiated using a * ThingDescription.Builder. - * - * The current version does not yet implement all the core vocabulary terms defined by the + * + * The current version does not yet implement all the core vocabulary terms defined by the * W3C Recommendation. */ public class ThingDescription { private final String title; private final List security; - + private final Optional uri; private final Set types; private final Optional baseURI; - + private final List properties; private final List actions; - + private final Optional graph; - + /** - * Supported serialization formats -- currently only RDF serialization formats, namely Turtle and + * Supported serialization formats -- currently only RDF serialization formats, namely Turtle and * JSON-LD 1.0. The version of JSON-LD currently supported is the one provided by RDF4J. */ public enum TDFormat { RDF_TURTLE, RDF_JSONLD }; - - protected ThingDescription(String title, List security, Optional uri, - Set types, Optional baseURI, List properties, + + protected ThingDescription(String title, List security, Optional uri, + Set types, Optional baseURI, List properties, List actions, Optional graph) { - + this.title = title; - + if (security.isEmpty()) { security.add(new NoSecurityScheme()); } @@ -62,66 +62,66 @@ protected ThingDescription(String title, List security, Optional this.uri = uri; this.types = types; this.baseURI = baseURI; - + this.properties = properties; this.actions = actions; - + this.graph = graph; } - + public String getTitle() { return title; } - + public List getSecuritySchemes() { return security; } - + /** * Gets the first {@link SecurityScheme} that matches a given semantic type. - * + * * @param type the type of the security scheme * @return an Optional with the security scheme (empty if not found) */ public Optional getFirstSecuritySchemeByType(String type) { return security.stream().filter(security -> security.getSchemeType().equals(type)).findFirst(); } - + public Optional getThingURI() { return uri; } - + public Set getSemanticTypes() { return types; } - + public Optional getBaseURI() { return baseURI; } - + /** - * Gets a set with all the semantic types of the action affordances provided by the described + * Gets a set with all the semantic types of the action affordances provided by the described * thing. - * + * * @return The set of semantic types, can be empty. */ public Set getSupportedActionTypes() { Set supportedActionTypes = new HashSet(); - + for (ActionAffordance action : actions) { supportedActionTypes.addAll(action.getSemanticTypes()); } - + return supportedActionTypes; } - + /** - * Gets a property affordance by name, which is specified using the td:name data + * Gets a property affordance by name, which is specified using the td:name data * property defined by the TD vocabulary. Names are mandatory in JSON-based representations. If a - * name is present, it is unique within the scope of a TD. - * - * @param name the name of the property affordance - * @return an Optional with the property affordance (empty if not found) + * name is present, it is unique within the scope of a TD. + * + * @param name the name of the property affordance + * @return an Optional with the property affordance (empty if not found) */ public Optional getPropertyByName(String name) { for (PropertyAffordance property : properties) { @@ -133,14 +133,14 @@ public Optional getPropertyByName(String name) { return Optional.empty(); } - + /** - * Gets a list of property affordances that have a {@link ch.unisg.ics.interactions.wot.td.affordances.Form} + * Gets a list of property affordances that have a {@link ch.unisg.ics.interactions.wot.td.affordances.Form} * hypermedia control for the given operation type. - * + * * The current implementation supports two operation types for properties: td:readProperty * and td:writeProperty. - * + * * @param operationType a string that captures the operation type * @return the list of property affordances */ @@ -148,10 +148,10 @@ public List getPropertiesByOperationType(String operationTyp return properties.stream().filter(property -> property.hasFormWithOperationType(operationType)) .collect(Collectors.toList()); } - + /** * Gets the first {@link PropertyAffordance} annotated with a given semantic type. - * + * * @param propertyType the semantic type, typically and IRI defined in some ontology * @return an Optional with the property affordance (empty if not found) */ @@ -161,17 +161,17 @@ public Optional getFirstPropertyBySemanticType(String proper return Optional.of(property); } } - + return Optional.empty(); } - + /** - * Gets an action affordance by name, which is specified using the td:name data + * Gets an action affordance by name, which is specified using the td:name data * property defined by the TD vocabulary. Names are mandatory in JSON-based representations. If a - * name is present, it is unique within the scope of a TD. - * - * @param name the name of the action affordance - * @return an Optional with the action affordance (empty if not found) + * name is present, it is unique within the scope of a TD. + * + * @param name the name of the action affordance + * @return an Optional with the action affordance (empty if not found) */ public Optional getActionByName(String name) { for (ActionAffordance action : actions) { @@ -180,17 +180,17 @@ public Optional getActionByName(String name) { return Optional.of(action); } } - + return Optional.empty(); } - + /** - * Gets a list of action affordances that have a {@link ch.unisg.ics.interactions.wot.td.affordances.Form} + * Gets a list of action affordances that have a {@link ch.unisg.ics.interactions.wot.td.affordances.Form} * hypermedia control for the given operation type. - * - * There is one operation type available actions: td:invokeAction. The API will be + * + * There is one operation type available actions: td:invokeAction. The API will be * simplified in future iterations. - * + * * @param operationType a string that captures the operation type * @return the list of property affordances */ @@ -198,10 +198,10 @@ public List getActionsByOperationType(String operationType) { return actions.stream().filter(action -> action.hasFormWithOperationType(operationType)) .collect(Collectors.toList()); } - + /** * Gets the first {@link ActionAffordance} annotated with a given semantic type. - * + * * @param actionType the semantic type, typically and IRI defined in some ontology * @return an Optional with the action affordance (empty if not found) */ @@ -211,126 +211,130 @@ public Optional getFirstActionBySemanticType(String actionType return Optional.of(action); } } - + return Optional.empty(); } - + public List getProperties() { return this.properties; } - + public List getActions() { return this.actions; } - + public Optional getGraph() { return graph; } - + /** * Helper class used to construct a ThingDescription. All TDs should have a mandatory - * title field. In addition to the optional fields defined by the W3C Recommendation, + * title field. In addition to the optional fields defined by the W3C Recommendation, * the addGraph method allows to add any other metadata as an RDF graph. - * + * * Implements a fluent API. */ public static class Builder { private final String title; private final List security; - + private Optional uri; private Optional baseURI; private final Set types; - + private final List properties; private final List actions; - + private Optional graph; - + public Builder(String title) { this.title = title; this.security = new ArrayList(); - + this.uri = Optional.empty(); this.baseURI = Optional.empty(); this.types= new HashSet(); - + this.properties = new ArrayList(); this.actions = new ArrayList(); - + this.graph = Optional.empty(); } - + public Builder addSecurityScheme(SecurityScheme security) { this.security.add(security); return this; } - + public Builder addSecuritySchemes(List security) { this.security.addAll(security); return this; } - + public Builder addThingURI(String uri) { this.uri = Optional.of(uri); return this; } - + public Builder addBaseURI(String baseURI) { this.baseURI = Optional.of(baseURI); return this; } - + public Builder addSemanticType(String type) { this.types.add(type); return this; } - + public Builder addSemanticTypes(Set thingTypes) { this.types.addAll(thingTypes); return this; } - + public Builder addProperty(PropertyAffordance property) { this.properties.add(property); return this; } - + public Builder addProperties(List properties) { this.properties.addAll(properties); return this; } - + public Builder addAction(ActionAffordance action) { this.actions.add(action); return this; } - + public Builder addActions(List actions) { this.actions.addAll(actions); return this; } - + /** - * Adds an RDF graph. If an RDF graph is already present, it will be merged with the new graph. - * + * Adds an RDF graph. If an RDF graph is already present, it will be merged with the new graph. + * * @param graph the RDF graph to be added * @return this Builder */ public Builder addGraph(Model graph) { if (this.graph.isPresent()) { this.graph.get().addAll(graph); + + graph.getNamespaces().stream() + .filter(ns -> !this.graph.get().getNamespace(ns.getPrefix()).isPresent()) + .forEach(this.graph.get()::setNamespace); } else { this.graph = Optional.of(graph); } - + return this; } - + /** * Convenience method used to add a single triple. If an RDF graph is already present, the triple * will be added to the existing graph. - * + * * @param subject the subject * @param predicate the predicate * @param object the object @@ -342,13 +346,13 @@ public Builder addTriple(Resource subject, IRI predicate, Value object) { } else { this.graph = Optional.of(new ModelBuilder().add(subject, predicate, object).build()); } - + return this; } - + /** * Constructs and returns a ThingDescription. - * + * * @return the constructed ThingDescription */ public ThingDescription build() { From cd2163e038e07922b7b0415b41208c723c715eaf Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 18:09:38 +0200 Subject: [PATCH 22/28] Add external graph with Thing as root --- .../wot/td/io/json/TDJsonWriter.java | 80 +++++++++++++++++-- .../wot/td/io/json/TDJsonWriterTest.java | 68 +++++++++++++++- 2 files changed, 139 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index b5c28577..67da9314 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -7,6 +7,12 @@ import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; import ch.unisg.ics.interactions.wot.td.io.AbstractTDWriter; import ch.unisg.ics.interactions.wot.td.vocabularies.TD; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Namespace; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; import javax.json.*; import java.io.ByteArrayOutputStream; @@ -34,6 +40,12 @@ public TDJsonWriter(ThingDescription td) { } public JsonObject getJson() { + + if (td.getGraph().isPresent()) { + td.getGraph().get().getNamespaces().stream() + .filter(ns -> !prefixMap.containsKey(ns.getName())) + .forEach(ns -> setNamespace(ns.getPrefix(), ns.getName())); + } if (semanticContext.isPresent()) { document.add(JWot.CONTEXT, Json.createArrayBuilder() .add(JWot.WOT_CONTEXT) @@ -78,11 +90,21 @@ public TDJsonWriter setNamespace(String prefix, String namespace) { protected TDJsonWriter addTypes() { //TODO This is ugly why is the types sometimes a set and sometimes a list? - if (td.getSemanticTypes().size() > 1) { - document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(td.getSemanticTypes()))); - } else if (!td.getSemanticTypes().isEmpty()) { + Set semanticTypes = td.getSemanticTypes(); + + if (td.getThingURI().isPresent()) { + Resource thingURI = SimpleValueFactory.getInstance().createIRI(td.getThingURI().get()); + td.getGraph().ifPresent(g -> g.getStatements(thingURI, RDF.TYPE, null) + .forEach(statement -> { + semanticTypes.add(statement.getObject().stringValue()); + })); + } + + if (semanticTypes.size() > 1) { + document.add(JWot.SEMANTIC_TYPE, this.getSemanticTypes(new ArrayList<>(semanticTypes))); + } else if (!semanticTypes.isEmpty()) { document.add(JWot.SEMANTIC_TYPE, - this.getPrefixedAnnotation(td.getSemanticTypes().stream().findFirst().orElse(""))); + this.getPrefixedAnnotation(semanticTypes.stream().findFirst().orElse(""))); } return this; } @@ -132,13 +154,55 @@ protected TDJsonWriter addActions() { @Override protected TDJsonWriter addGraph() { - td.getGraph().ifPresent(g -> g.getStatements(null, null, null).forEach(statement -> { - //TODO I'm not sure this is the right way to parse the statement - document.add(getPrefixedAnnotation(statement.getPredicate().stringValue()), statement.getObject().stringValue()); - })); + + if (td.getThingURI().isPresent()) { + Resource thingURI = SimpleValueFactory.getInstance().createIRI(td.getThingURI().get()); + td.getGraph().ifPresent(g -> g.getStatements(thingURI, null, null) + .forEach(statement -> { + + if (!statement.getPredicate().equals(RDF.TYPE)) { + String prefixedPredicate = getPrefixedAnnotation(statement.getPredicate().stringValue()); + Value object = statement.getObject(); + + if (!object.isBNode()) { + document.add(prefixedPredicate, getPrefixedAnnotation(object.stringValue())); + } + else { + JsonObjectBuilder objectObjBuilder = getStatementObject((Resource) object); + document.add(prefixedPredicate, objectObjBuilder); + } + } + })); + } + return this; } + protected JsonObjectBuilder getStatementObject(Resource subject) { + JsonObjectBuilder subjectObjBuilder = Json.createObjectBuilder(); + + td.getGraph().ifPresent(g -> g.getStatements(subject, null, null) + .forEach(statement -> { + IRI predicate = statement.getPredicate(); + Value object = statement.getObject(); + + if (!object.isBNode()) { + if (predicate.equals(RDF.TYPE)) { + subjectObjBuilder.add(JWot.SEMANTIC_TYPE,getPrefixedAnnotation(object.stringValue())); + } else { + subjectObjBuilder.add(getPrefixedAnnotation(predicate.stringValue()), + getPrefixedAnnotation(object.stringValue())); + } + } + else { + JsonObjectBuilder objectObjBuilder = getStatementObject((Resource) object); + subjectObjBuilder.add(getPrefixedAnnotation(predicate.stringValue()), objectObjBuilder); + } + })); + + return subjectObjBuilder; + } + private String getPrefixedAnnotation(String annotation) { if (annotation.startsWith(TD.PREFIX)) { diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 14e86bf9..4eeab021 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -8,6 +8,14 @@ import ch.unisg.ics.interactions.wot.td.schemas.StringSchema; import ch.unisg.ics.interactions.wot.td.security.NoSecurityScheme; import ch.unisg.ics.interactions.wot.td.vocabularies.TD; +import org.eclipse.rdf4j.model.BNode; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.LinkedHashModel; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.util.ModelBuilder; +import org.eclipse.rdf4j.model.vocabulary.DCTERMS; +import org.eclipse.rdf4j.model.vocabulary.RDF; import org.junit.Assert; import org.junit.Test; @@ -90,7 +98,7 @@ public void testThingWithSemanticTypes() { .add("https://www.w3.org/2019/wot/td/v1") .add(Json.createObjectBuilder() .add("eve", "http://w3id.org/eve#") - .add("iot", "http://iotschema.org/").build()).build()) + .add("iot", "http://iotschema.org/"))) .add("@type", Json.createArrayBuilder().add("eve:Artifact").add("iot:Light")) .add("title", THING_TITLE) .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) @@ -279,6 +287,7 @@ public void testThingPropertiesWithOneSemanticType() { JsonObject test = new TDJsonWriter(td) .setNamespace("ex", "http://example.org/") .getJson(); + Assert.assertEquals(expected, test); } @@ -413,6 +422,63 @@ public void testThingActionsWithSemanticTypes() { .setNamespace("ex1", "http://example.org/1/") .setNamespace("ex2", "http://example.org/2/") .getJson(); + + Assert.assertEquals(expected, test); + } + + @Test + public void testWriteAdditionalMetadata() { + + ValueFactory rdf = SimpleValueFactory.getInstance(); + Model metadata = new LinkedHashModel(); + + final String NS = "http://w3id.org/eve#"; + metadata.setNamespace("eve", NS); + + BNode manualId = rdf.createBNode(); + BNode protocolId = rdf.createBNode(); + metadata.add(rdf.createIRI("http://example.org/lamp123"), rdf.createIRI(NS,"hasManual"), manualId); + metadata.add(manualId, RDF.TYPE, rdf.createIRI(NS, "Manual")); + //metadata.add(manualId, DCTERMS.TITLE, rdf.createLiteral("My Lamp Manual")); + + ThingDescription td = new ThingDescription.Builder("My Thing") + .addThingURI("http://example.org/lamp123") + .addSemanticType("https://saref.etsi.org/core/LightSwitch") + .addTriple(rdf.createIRI("http://example.org/lamp123"), RDF.TYPE, rdf.createIRI(NS, + "Artifact")) + .addTriple(protocolId, RDF.TYPE, rdf.createIRI(NS, "UsageProtocol")) + //.addTriple(protocolId, DCTERMS.TITLE, rdf.createLiteral("Party Light")) + .addGraph(metadata) + .addGraph(new ModelBuilder() + .add(manualId, rdf.createIRI(NS, "hasUsageProtocol"), protocolId) + .build()) + .addTriple(protocolId, rdf.createIRI(NS,"hasLanguage"), rdf.createIRI("http://jason.sourceforge.net/wp/description/")) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .add("@context", Json.createArrayBuilder() + .add("https://www.w3.org/2019/wot/td/v1") + .add(Json.createObjectBuilder() + .add("saref", "https://saref.etsi.org/core/") + .add( "eve", "http://w3id.org/eve#"))) + .add("id", "http://example.org/lamp123") + .add("title", THING_TITLE) + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .add("@type", Json.createArrayBuilder() + .add("eve:Artifact") + .add("saref:LightSwitch")) + .add("eve:hasManual" , Json.createObjectBuilder() + .add("@type","eve:Manual") + .add("eve:hasUsageProtocol", Json.createObjectBuilder() + .add("@type", "eve:UsageProtocol") + .add("eve:hasLanguage", "http://jason.sourceforge.net/wp/description/"))) + .build(); + + JsonObject test = new TDJsonWriter(td) + .setNamespace("saref", "https://saref.etsi.org/core/") + .getJson(); + Assert.assertEquals(expected, test); } From b14a07e2ad05ef454f5e232de6ae5fdf0e2ce923 Mon Sep 17 00:00:00 2001 From: danaivach Date: Mon, 20 Sep 2021 18:39:11 +0200 Subject: [PATCH 23/28] Deserialize sem types as Set --- .../wot/td/affordances/ActionAffordance.java | 48 ++-- .../interactions/wot/td/affordances/Form.java | 75 +++-- .../td/affordances/InteractionAffordance.java | 80 +++--- .../td/affordances/PropertyAffordance.java | 35 ++- .../td/affordances/ActionAffordanceTest.java | 26 +- .../InteractionAffordanceTest.java | 40 +-- .../wot/td/io/graph/TDGraphReaderTest.java | 272 +++++++++--------- 7 files changed, 280 insertions(+), 296 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordance.java b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordance.java index 3bf22a36..f47a2c7f 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordance.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordance.java @@ -1,79 +1,75 @@ package ch.unisg.ics.interactions.wot.td.affordances; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - import ch.unisg.ics.interactions.wot.td.schemas.DataSchema; import ch.unisg.ics.interactions.wot.td.vocabularies.TD; +import java.util.*; + /** * TODO: add javadoc - * - * @author Andrei Ciortea * + * @author Andrei Ciortea */ public class ActionAffordance extends InteractionAffordance { final private Optional input; final private Optional output; - + // TODO: add safe, idempotent - - private ActionAffordance(Optional name, Optional title, List types, - List forms, Optional input, Optional output) { + + private ActionAffordance(Optional name, Optional title, Set types, + List forms, Optional input, Optional output) { super(name, title, types, forms); this.input = input; this.output = output; } - + public Optional getFirstForm() { return getFirstFormForOperationType(TD.invokeAction); } - + public Optional getInputSchema() { return input; } - + public Optional getOutputSchema() { return output; } - - public static class Builder - extends InteractionAffordance.Builder { - + + public static class Builder + extends InteractionAffordance.Builder { + private Optional inputSchema; private Optional outputSchema; - + public Builder(List forms) { super(forms); - + for (Form form : this.forms) { form.addOperationType(TD.invokeAction); - + if (!form.getMethodName().isPresent()) { form.setMethodName("POST"); } } - + this.inputSchema = Optional.empty(); this.outputSchema = Optional.empty(); } - + public Builder(Form form) { this(new ArrayList(Arrays.asList(form))); } - + public Builder addInputSchema(DataSchema inputSchema) { this.inputSchema = Optional.of(inputSchema); return this; } - + public Builder addOutputSchema(DataSchema outputSchema) { this.outputSchema = Optional.of(outputSchema); return this; } - + public ActionAffordance build() { return new ActionAffordance(name, title, types, forms, inputSchema, outputSchema); } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/Form.java b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/Form.java index 84054a5e..e63e2eff 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/Form.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/Form.java @@ -1,5 +1,7 @@ package ch.unisg.ics.interactions.wot.td.affordances; +import ch.unisg.ics.interactions.wot.td.vocabularies.TD; + import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -7,24 +9,20 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import ch.unisg.ics.interactions.wot.td.vocabularies.TD; - public class Form { - private static final Map DEFAULT_HTTP_BINDING = Stream.of(new String[][] { - { TD.readProperty, "GET" }, - { TD.writeProperty, "PUT" }, - { TD.invokeAction, "POST" }, + private static final Map DEFAULT_HTTP_BINDING = Stream.of(new String[][]{ + {TD.readProperty, "GET"}, + {TD.writeProperty, "PUT"}, + {TD.invokeAction, "POST"}, }).collect(Collectors.toMap(data -> data[0], data -> data[1])); - - private Optional methodName; private final String target; private final String contentType; private final Set operationTypes; - private final Optional subprotocol; - + private Optional methodName; + private Form(String href, Optional methodName, String mediaType, Set operationTypes, - Optional subprotocol) { + Optional subprotocol) { this.methodName = methodName; this.target = href; this.contentType = mediaType; @@ -35,23 +33,28 @@ private Form(String href, Optional methodName, String mediaType, Set getMethodName() { return methodName; } - + + // Package-level access, used for setting affordance-specific default values after instantiation + void setMethodName(String methodName) { + this.methodName = Optional.of(methodName); + } + public Optional getMethodName(String operationType) { if (!operationTypes.contains(operationType)) { throw new IllegalArgumentException("Unknown operation type: " + operationType); } - + if (methodName.isPresent()) { return methodName; } - + if (DEFAULT_HTTP_BINDING.containsKey(operationType)) { return Optional.of(DEFAULT_HTTP_BINDING.get(operationType)); } - + return Optional.empty(); } - + public String getTarget() { return target; } @@ -59,37 +62,31 @@ public String getTarget() { public String getContentType() { return contentType; } - + public boolean hasOperationType(String type) { return operationTypes.contains(type); } - + public Set getOperationTypes() { return operationTypes; } - + public Optional getSubProtocol() { return this.subprotocol; } - - // Package-level access, used for setting affordance-specific default values after instantiation - void setMethodName(String methodName) { - this.methodName = Optional.of(methodName); - } - + // Package-level access, used for setting affordance-specific default values after instantiation void addOperationType(String operationType) { this.operationTypes.add(operationType); } - + public static class Builder { private final String target; + private final Set operationTypes; private Optional methodName; private String contentType; - private final Set operationTypes; - private Optional subprotocol; - + public Builder(String target) { this.target = target; this.methodName = Optional.empty(); @@ -97,37 +94,37 @@ public Builder(String target) { this.operationTypes = new HashSet(); this.subprotocol = Optional.empty(); } - + public Builder addOperationType(String operationType) { this.operationTypes.add(operationType); return this; } - + public Builder addOperationTypes(Set operationTypes) { this.operationTypes.addAll(operationTypes); return this; } - + public Builder setMethodName(String methodName) { this.methodName = Optional.of(methodName); return this; } - + public Builder setContentType(String contentType) { this.contentType = contentType; return this; } - + public Builder addSubProtocol(String subprotocol) { this.subprotocol = Optional.of(subprotocol); return this; } - + public Form build() { - return new Form(this.target, this.methodName, this.contentType, this.operationTypes, - this.subprotocol); + return new Form(this.target, this.methodName, this.contentType, this.operationTypes, + this.subprotocol); } - + } - + } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordance.java b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordance.java index 7271dbb3..a9524220 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordance.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordance.java @@ -1,145 +1,143 @@ package ch.unisg.ics.interactions.wot.td.affordances; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; /** * TODO: add javadoc - * - * @author Andrei Ciortea * + * @author Andrei Ciortea */ public class InteractionAffordance { public static final String PROPERTY = "property"; public static final String EVENT = "event"; public static final String ACTION = "action"; - + protected Optional name; protected Optional title; - protected List types; + protected Set types; protected List forms; - - protected InteractionAffordance(Optional name, Optional title, List types, - List forms) { + + protected InteractionAffordance(Optional name, Optional title, Set types, + List forms) { this.name = name; this.title = title; this.types = types; this.forms = forms; } - + public Optional getName() { return name; } - + public Optional getTitle() { return title; } - - public List getSemanticTypes() { + + public Set getSemanticTypes() { return types; } - + public List getForms() { return forms; } - + public boolean hasFormWithOperationType(String operationType) { return !forms.stream().filter(form -> form.hasOperationType(operationType)) - .collect(Collectors.toList()).isEmpty(); + .collect(Collectors.toList()).isEmpty(); } - + public Optional getFirstFormForOperationType(String operationType) { if (hasFormWithOperationType(operationType)) { Form firstForm = forms.stream().filter(form -> form.hasOperationType(operationType)) - .collect(Collectors.toList()).get(0); - + .collect(Collectors.toList()).get(0); + return Optional.of(firstForm); } - + return Optional.empty(); } - + public boolean hasSemanticType(String type) { return this.types.contains(type); } - + public boolean hasOneSemanticType(List types) { for (String type : types) { if (this.types.contains(type)) { return true; } } - + return false; } - + public boolean hasAllSemanticTypes(List types) { for (String type : types) { if (!this.types.contains(type)) { return false; } } - + return true; } - - /** Abstract builder for interaction affordances. */ - public static abstract class Builder> { + + /** + * Abstract builder for interaction affordances. + */ + public static abstract class Builder> { protected Optional name; protected Optional title; - protected List types; + protected Set types; protected List forms; - + protected Builder(Form form) { this(new ArrayList(Arrays.asList(form))); } - + protected Builder(List forms) { this.name = Optional.empty(); this.title = Optional.empty(); - this.types = new ArrayList(); + this.types = new HashSet(); this.forms = forms; } - + @SuppressWarnings("unchecked") public S addName(String name) { this.name = Optional.of(name); return (S) this; } - + @SuppressWarnings("unchecked") public S addTitle(String title) { this.title = Optional.of(title); return (S) this; } - + @SuppressWarnings("unchecked") public S addSemanticType(String type) { this.types.add(type); return (S) this; } - + @SuppressWarnings("unchecked") public S addSemanticTypes(List types) { this.types.addAll(types); return (S) this; } - + @SuppressWarnings("unchecked") public S addForm(Form form) { this.forms.add(form); return (S) this; } - + @SuppressWarnings("unchecked") public S addForms(List forms) { this.forms.addAll(forms); return (S) this; } - + public abstract T build(); } } diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/PropertyAffordance.java b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/PropertyAffordance.java index 487c8f0c..92f32220 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/PropertyAffordance.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/affordances/PropertyAffordance.java @@ -1,61 +1,58 @@ package ch.unisg.ics.interactions.wot.td.affordances; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - import ch.unisg.ics.interactions.wot.td.schemas.DataSchema; import ch.unisg.ics.interactions.wot.td.vocabularies.TD; +import java.util.*; + public class PropertyAffordance extends InteractionAffordance { private final DataSchema schema; private final boolean observable; - - private PropertyAffordance(Optional name, DataSchema schema, boolean observable, - Optional title, List types, List forms) { + + private PropertyAffordance(Optional name, DataSchema schema, boolean observable, + Optional title, Set types, List forms) { super(name, title, types, forms); this.schema = schema; this.observable = observable; } - + public DataSchema getDataSchema() { return schema; } - + public boolean isObservable() { return observable; } - - public static class Builder - extends InteractionAffordance.Builder { + + public static class Builder + extends InteractionAffordance.Builder { private final DataSchema schema; private boolean observable; - + public Builder(DataSchema schema, List forms) { super(forms); - + for (Form form : this.forms) { if (form.getOperationTypes().isEmpty()) { form.addOperationType(TD.readProperty); form.addOperationType(TD.writeProperty); } } - + this.schema = schema; this.observable = false; } - + public Builder(DataSchema schema, Form form) { this(schema, new ArrayList(Arrays.asList(form))); } - + public Builder addObserve() { this.observable = true; return this; } - + @Override public PropertyAffordance build() { return new PropertyAffordance(name, schema, observable, title, types, forms); diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordanceTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordanceTest.java index 8077118f..c6fe2032 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordanceTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/ActionAffordanceTest.java @@ -13,10 +13,10 @@ import ch.unisg.ics.interactions.wot.td.vocabularies.TD; public class ActionAffordanceTest { - + private ActionAffordance testAction; private Form form; - + @Before public void init() { form = new Form.Builder("http://example.org/action").build(); @@ -29,46 +29,46 @@ public void testOneForm() { assertEquals(1, forms.size()); assertEquals(form, forms.get(0)); } - + @Test public void testMultipleForms() { Form form1 = new Form.Builder("http://example.org") .setMethodName("GET") .setContentType("application/json") .build(); - + Form form2 = new Form.Builder("http://example.org") .setMethodName("POST") .setContentType("application/json") .build(); - + Form form3 = new Form.Builder("http://example.org") .setMethodName("PUT") .setContentType("application/json") .build(); - + List formList = new ArrayList(Arrays.asList(form1, form2, form3)); - + ActionAffordance action = new ActionAffordance.Builder(formList).build(); List forms = action.getForms(); - + assertEquals(3, forms.size()); assertEquals(form1, forms.get(0)); assertEquals(form2, forms.get(1)); assertEquals(form3, forms.get(2)); } - + @Test public void testDefaultValues() { String invokeAction = TD.invokeAction; assertTrue(testAction.hasFormWithOperationType(invokeAction)); assertEquals("POST", testAction.getForms().get(0).getMethodName(invokeAction).get()); } - + @Test public void testFullOptionAction() { Form form = new Form.Builder("http://example.org").setMethodName("GET").build(); - + ActionAffordance action = new ActionAffordance.Builder(form) .addName("turn_on") .addTitle("Turn on") @@ -76,10 +76,10 @@ public void testFullOptionAction() { // TODO: add schema as well //.addInputSchema(inputSchema) .build(); - + assertEquals("turn_on", action.getName().get()); assertEquals("Turn on", action.getTitle().get()); assertEquals(1, action.getSemanticTypes().size()); - assertEquals("iot:TurnOn", action.getSemanticTypes().get(0)); + assertTrue(action.getSemanticTypes().contains("iot:TurnOn")); } } diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordanceTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordanceTest.java index 59314e8f..8ddc461d 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordanceTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/affordances/InteractionAffordanceTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; +import java.util.HashSet; import java.util.Optional; import org.junit.Before; @@ -14,73 +15,74 @@ public class InteractionAffordanceTest { private static final String prefix = "http://example.org"; private InteractionAffordance test_affordance; - + @Before public void init() { Form form1 = new Form.Builder("http://example.org/property1") .addOperationType(TD.readProperty) .build(); - + Form form2 = new Form.Builder("http://example.org/property2") .addOperationType(TD.writeProperty) .build(); - - test_affordance = new InteractionAffordance(Optional.of("my_affordance"), - Optional.of("My Affordance"), Arrays.asList(prefix + "Type1", prefix + "Type2"), + + test_affordance = new InteractionAffordance(Optional.of("my_affordance"), + Optional.of("My Affordance"), new HashSet<>(Arrays.asList(prefix + "Type1", prefix + + "Type2")), Arrays.asList(form1, form2)); } - + @Test public void testHasFormForOperationType() { assertTrue(test_affordance.hasFormWithOperationType(TD.readProperty)); assertTrue(test_affordance.hasFormWithOperationType(TD.writeProperty)); } - + @Test public void testNoFormByOperationType() { assertFalse(test_affordance.hasFormWithOperationType("bla")); } - + @Test public void testHasSemanticType() { assertTrue(test_affordance.hasSemanticType(prefix + "Type1")); } - + @Test public void testHasNotSemanticType() { assertFalse(test_affordance.hasSemanticType(prefix + "Type0")); } - + @Test public void testHasOneSemanticType() { - assertTrue(test_affordance.hasOneSemanticType(Arrays.asList(prefix + "Type0", + assertTrue(test_affordance.hasOneSemanticType(Arrays.asList(prefix + "Type0", prefix + "Type1"))); } - + @Test public void testHasNotOneSemanticType() { - assertFalse(test_affordance.hasOneSemanticType(Arrays.asList(prefix + "Type3", + assertFalse(test_affordance.hasOneSemanticType(Arrays.asList(prefix + "Type3", prefix + "Type4"))); } - + @Test public void testHasAllSemanticTypes() { - assertTrue(test_affordance.hasAllSemanticTypes(Arrays.asList(prefix + "Type1", + assertTrue(test_affordance.hasAllSemanticTypes(Arrays.asList(prefix + "Type1", prefix + "Type2"))); } - + @Test public void testHasNotAllSemanticTypes() { - assertFalse(test_affordance.hasAllSemanticTypes(Arrays.asList(prefix + "Type1", + assertFalse(test_affordance.hasAllSemanticTypes(Arrays.asList(prefix + "Type1", prefix + "Type2", prefix + "Type3"))); } - + @Test public void testGetFirstFormForOperationType() { assertTrue(test_affordance.getFirstFormForOperationType(TD.readProperty) .isPresent()); } - + @Test public void testNoFirstFormForOperationType() { assertFalse(test_affordance.getFirstFormForOperationType(TD.invokeAction) diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReaderTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReaderTest.java index 0878d790..214ed5a6 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReaderTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/graph/TDGraphReaderTest.java @@ -1,26 +1,11 @@ package ch.unisg.ics.interactions.wot.td.io.graph; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import ch.unisg.ics.interactions.wot.td.io.InvalidTDException; -import ch.unisg.ics.interactions.wot.td.io.graph.TDGraphReader; -import org.eclipse.rdf4j.rio.RDFFormat; -import org.junit.Test; - import ch.unisg.ics.interactions.wot.td.ThingDescription; import ch.unisg.ics.interactions.wot.td.ThingDescription.TDFormat; import ch.unisg.ics.interactions.wot.td.affordances.ActionAffordance; import ch.unisg.ics.interactions.wot.td.affordances.Form; import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; +import ch.unisg.ics.interactions.wot.td.io.InvalidTDException; import ch.unisg.ics.interactions.wot.td.schemas.DataSchema; import ch.unisg.ics.interactions.wot.td.schemas.IntegerSchema; import ch.unisg.ics.interactions.wot.td.schemas.NumberSchema; @@ -30,11 +15,20 @@ import ch.unisg.ics.interactions.wot.td.security.SecurityScheme; import ch.unisg.ics.interactions.wot.td.vocabularies.TD; import ch.unisg.ics.interactions.wot.td.vocabularies.WoTSec; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; public class TDGraphReaderTest { private static final String TEST_SIMPLE_TD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix htv: .\n" + "@prefix hctl: .\n" + "@prefix dct: .\n" + @@ -92,97 +86,97 @@ public class TDGraphReaderTest { " ] ;\n" + " js:required \"boolean_value\" ;\n" + " ]\n" + - " ] ." ; + " ] ."; private static final String TEST_SIMPLE_TD_JSONLD = "[ {\n" + - " \"@id\" : \"_:node1ea75dfphx111\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/security#NoSecurityScheme\" ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx112\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/td#ActionAffordance\" ],\n" + - " \"http://purl.org/dc/terms/title\" : [ {\n" + - " \"@value\" : \"My Action\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasForm\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx113\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasInputSchema\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx114\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasOutputSchema\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx116\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx113\",\n" + - " \"http://www.w3.org/2011/http#methodName\" : [ {\n" + - " \"@value\" : \"PUT\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/hypermedia#forContentType\" : [ {\n" + - " \"@value\" : \"application/json\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/hypermedia#hasOperationType\" : [ {\n" + - " \"@id\" : \"https://www.w3.org/2019/wot/td#invokeAction\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/hypermedia#hasTarget\" : [ {\n" + - " \"@id\" : \"http://example.org/action\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx114\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx115\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + - " \"@value\" : \"number_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx115\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#NumberSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#maximum\" : [ {\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + - " \"@value\" : \"100.05\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#minimum\" : [ {\n" + - " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + - " \"@value\" : \"-100.05\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + - " \"@value\" : \"number_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx116\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx117\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + - " \"@value\" : \"boolean_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"_:node1ea75dfphx117\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#BooleanSchema\" ],\n" + - " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + - " \"@value\" : \"boolean_value\"\n" + - " } ]\n" + - "}, {\n" + - " \"@id\" : \"http://example.org/#thing\",\n" + - " \"@type\" : [ \"https://www.w3.org/2019/wot/td#Thing\" ],\n" + - " \"http://purl.org/dc/terms/title\" : [ {\n" + - " \"@value\" : \"My Thing\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasActionAffordance\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx112\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasBase\" : [ {\n" + - " \"@id\" : \"http://example.org/\"\n" + - " } ],\n" + - " \"https://www.w3.org/2019/wot/td#hasSecurityConfiguration\" : [ {\n" + - " \"@id\" : \"_:node1ea75dfphx111\"\n" + - " } ]\n" + - "} ]"; + " \"@id\" : \"_:node1ea75dfphx111\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/security#NoSecurityScheme\" ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx112\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/td#ActionAffordance\" ],\n" + + " \"http://purl.org/dc/terms/title\" : [ {\n" + + " \"@value\" : \"My Action\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasForm\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx113\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasInputSchema\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx114\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasOutputSchema\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx116\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx113\",\n" + + " \"http://www.w3.org/2011/http#methodName\" : [ {\n" + + " \"@value\" : \"PUT\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/hypermedia#forContentType\" : [ {\n" + + " \"@value\" : \"application/json\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/hypermedia#hasOperationType\" : [ {\n" + + " \"@id\" : \"https://www.w3.org/2019/wot/td#invokeAction\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/hypermedia#hasTarget\" : [ {\n" + + " \"@id\" : \"http://example.org/action\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx114\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx115\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + + " \"@value\" : \"number_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx115\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#NumberSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#maximum\" : [ {\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + + " \"@value\" : \"100.05\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#minimum\" : [ {\n" + + " \"@type\" : \"http://www.w3.org/2001/XMLSchema#decimal\",\n" + + " \"@value\" : \"-100.05\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + + " \"@value\" : \"number_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx116\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#ObjectSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#properties\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx117\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#required\" : [ {\n" + + " \"@value\" : \"boolean_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"_:node1ea75dfphx117\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/json-schema#BooleanSchema\" ],\n" + + " \"https://www.w3.org/2019/wot/json-schema#propertyName\" : [ {\n" + + " \"@value\" : \"boolean_value\"\n" + + " } ]\n" + + "}, {\n" + + " \"@id\" : \"http://example.org/#thing\",\n" + + " \"@type\" : [ \"https://www.w3.org/2019/wot/td#Thing\" ],\n" + + " \"http://purl.org/dc/terms/title\" : [ {\n" + + " \"@value\" : \"My Thing\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasActionAffordance\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx112\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasBase\" : [ {\n" + + " \"@id\" : \"http://example.org/\"\n" + + " } ],\n" + + " \"https://www.w3.org/2019/wot/td#hasSecurityConfiguration\" : [ {\n" + + " \"@id\" : \"_:node1ea75dfphx111\"\n" + + " } ]\n" + + "} ]"; private static final String TEST_IO_HEAD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix htv: .\n" + "@prefix hctl: .\n" + "@prefix dct: .\n" + @@ -203,7 +197,7 @@ public class TDGraphReaderTest { " hctl:hasOperationType td:invokeAction;\n" + " ] ;\n"; - private static final String TEST_IO_TAIL = " ] ." ; + private static final String TEST_IO_TAIL = " ] ."; @Test public void testReadTitle() { @@ -234,13 +228,13 @@ public void testReadOneSecurityScheme() { assertEquals(1, reader.readSecuritySchemes().size()); assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> - scheme.getSchemeType().equals(WoTSec.NoSecurityScheme))); + scheme.getSchemeType().equals(WoTSec.NoSecurityScheme))); } @Test public void testReadAPIKeySecurityScheme() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix wotsec: .\n" + "@prefix dct: .\n" + "\n" + @@ -257,7 +251,7 @@ public void testReadAPIKeySecurityScheme() { SecurityScheme scheme = reader.readSecuritySchemes().iterator().next(); assertTrue(scheme instanceof APIKeySecurityScheme); - assertEquals(WoTSec.APIKeySecurityScheme, ((APIKeySecurityScheme) scheme).getSchemeType()); + assertEquals(WoTSec.APIKeySecurityScheme, scheme.getSchemeType()); assertEquals(TokenLocation.HEADER, ((APIKeySecurityScheme) scheme).getIn()); assertEquals("X-API-Key", ((APIKeySecurityScheme) scheme).getName().get()); } @@ -265,7 +259,7 @@ public void testReadAPIKeySecurityScheme() { @Test public void testAPIKeySecuritySchemeDefaultValues() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix wotsec: .\n" + "@prefix dct: .\n" + "\n" + @@ -276,7 +270,7 @@ public void testAPIKeySecuritySchemeDefaultValues() { TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, testTD); assertEquals(1, reader.readSecuritySchemes().size()); SecurityScheme scheme = reader.readSecuritySchemes().iterator().next(); - assertEquals(WoTSec.APIKeySecurityScheme, ((APIKeySecurityScheme) scheme).getSchemeType()); + assertEquals(WoTSec.APIKeySecurityScheme, scheme.getSchemeType()); assertEquals(TokenLocation.QUERY, ((APIKeySecurityScheme) scheme).getIn()); assertFalse(((APIKeySecurityScheme) scheme).getName().isPresent()); } @@ -284,7 +278,7 @@ public void testAPIKeySecuritySchemeDefaultValues() { @Test(expected = InvalidTDException.class) public void testAPIKeySecuritySchemeInvalidTokenLocation() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix wotsec: .\n" + "@prefix dct: .\n" + "\n" + @@ -300,7 +294,7 @@ public void testAPIKeySecuritySchemeInvalidTokenLocation() { @Test public void testReadMultipleSecuritySchemes() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix htv: .\n" + "@prefix wotsec: .\n" + "@prefix dct: .\n" + @@ -315,9 +309,9 @@ public void testReadMultipleSecuritySchemes() { TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, testTD); assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType() - .equals(WoTSec.NoSecurityScheme))); + .equals(WoTSec.NoSecurityScheme))); assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType() - .equals(WoTSec.APIKeySecurityScheme))); + .equals(WoTSec.APIKeySecurityScheme))); } @Test @@ -349,7 +343,7 @@ public void testReadOneSimpleAction() { assertEquals("my_action", action.getName().get()); assertEquals("My Action", action.getTitle().get()); assertEquals(1, action.getSemanticTypes().size()); - assertEquals(TD.ActionAffordance, action.getSemanticTypes().get(0)); + assertTrue(action.getSemanticTypes().contains(TD.ActionAffordance)); assertEquals(1, action.getForms().size()); Form form = action.getForms().get(0); @@ -360,7 +354,7 @@ public void testReadOneSimpleAction() { @Test public void testReadMultipleSimpleActions() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix htv: .\n" + "@prefix hctl: .\n" + "@prefix dct: .\n" + @@ -400,14 +394,14 @@ public void testReadMultipleSimpleActions() { " hctl:forContentType \"application/json\";\n" + " hctl:hasOperationType td:invokeAction;\n" + " ] ;\n" + - " ] ." ; + " ] ."; TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, testTD); assertEquals(3, reader.readActions().size()); List actionTitles = reader.readActions().stream().map(action -> action.getTitle().get()) - .collect(Collectors.toList()); + .collect(Collectors.toList()); assertTrue(actionTitles.contains("First Action")); assertTrue(actionTitles.contains("Second Action")); @@ -417,7 +411,7 @@ public void testReadMultipleSimpleActions() { @Test public void testReadOneActionOneObjectInput() { String testSimpleObject = - " td:hasInputSchema [\n" + + " td:hasInputSchema [\n" + " a js:ObjectSchema ;\n" + " js:properties [\n" + " a js:BooleanSchema ;\n" + @@ -447,7 +441,7 @@ public void testReadOneActionOneObjectInput() { " ]\n"; TDGraphReader reader = new TDGraphReader(RDFFormat.TURTLE, TEST_IO_HEAD + testSimpleObject - + TEST_IO_TAIL); + + TEST_IO_TAIL); ActionAffordance action = reader.readActions().get(0); @@ -484,13 +478,13 @@ public void testReadOneActionOneObjectInput() { @Test public void testReadTDFromFile() throws IOException { - // Read a TD from a File by passing its path as parameter - ThingDescription simple = TDGraphReader.readFromFile(TDFormat.RDF_TURTLE, "samples/simple_td.ttl"); - ThingDescription forklift = TDGraphReader.readFromFile(TDFormat.RDF_TURTLE, "samples/forkliftRobot.ttl"); + // Read a TD from a File by passing its path as parameter + ThingDescription simple = TDGraphReader.readFromFile(TDFormat.RDF_TURTLE, "samples/simple_td.ttl"); + ThingDescription forklift = TDGraphReader.readFromFile(TDFormat.RDF_TURTLE, "samples/forkliftRobot.ttl"); - // Check if a TD was created from the file by checking its title - assertEquals("My Thing", simple.getTitle()); - assertEquals("forkliftRobot", forklift.getTitle()); + // Check if a TD was created from the file by checking its title + assertEquals("My Thing", simple.getTitle()); + assertEquals("forkliftRobot", forklift.getTitle()); } @Test @@ -503,7 +497,7 @@ public void testReadSimpleFullTD() { assertEquals(1, td.getSemanticTypes().size()); assertTrue(td.getSemanticTypes().contains("https://www.w3.org/2019/wot/td#Thing")); assertTrue(td.getSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType() - .equals(WoTSec.NoSecurityScheme))); + .equals(WoTSec.NoSecurityScheme))); assertEquals(1, td.getActions().size()); // Check action metadata @@ -536,26 +530,26 @@ public void testReadSimpleFullTD() { @Test public void testMissingMandatoryTitle() { - String testTDWithMissingTitle = - "@prefix td: .\n" + - "@prefix wotsec: .\n" + - "\n" + - " a td:Thing ;\n" + - " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + - " td:hasBase .\n" ; - - Exception exception = assertThrows(InvalidTDException.class, () -> { - TDGraphReader.readFromString(TDFormat.RDF_TURTLE, testTDWithMissingTitle); + String testTDWithMissingTitle = + "@prefix td: .\n" + + "@prefix wotsec: .\n" + + "\n" + + " a td:Thing ;\n" + + " td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] ;\n" + + " td:hasBase .\n"; + + Exception exception = assertThrows(InvalidTDException.class, () -> { + TDGraphReader.readFromString(TDFormat.RDF_TURTLE, testTDWithMissingTitle); }); - String expectedMessage = "Missing mandatory title."; + String expectedMessage = "Missing mandatory title."; String actualMessage = exception.getMessage(); assertTrue(actualMessage.contains(expectedMessage)); } private void assertForm(Form form, String methodName, String target, - String contentType, String operationType) { + String contentType, String operationType) { assertEquals(methodName, form.getMethodName().get()); assertEquals(target, form.getTarget()); assertEquals(contentType, form.getContentType()); From e1dfb6c1994c9880accc11ac787f3295c0a1031e Mon Sep 17 00:00:00 2001 From: danaivach Date: Tue, 21 Sep 2021 13:18:34 +0200 Subject: [PATCH 24/28] Add TODOs --- .../ics/interactions/wot/td/io/json/TDJsonWriter.java | 10 ++++++---- .../interactions/wot/td/io/json/TDJsonWriterTest.java | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 67da9314..b0afd555 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -154,22 +154,23 @@ protected TDJsonWriter addActions() { @Override protected TDJsonWriter addGraph() { - + // TODO the getStatementObject can be exapnded so that addGraph() calls directly : + // document.addAll(getStatementObject(thingURI); if (td.getThingURI().isPresent()) { Resource thingURI = SimpleValueFactory.getInstance().createIRI(td.getThingURI().get()); td.getGraph().ifPresent(g -> g.getStatements(thingURI, null, null) .forEach(statement -> { if (!statement.getPredicate().equals(RDF.TYPE)) { - String prefixedPredicate = getPrefixedAnnotation(statement.getPredicate().stringValue()); + IRI predicate = statement.getPredicate(); Value object = statement.getObject(); if (!object.isBNode()) { - document.add(prefixedPredicate, getPrefixedAnnotation(object.stringValue())); + document.add(getPrefixedAnnotation(predicate.stringValue()), getPrefixedAnnotation(object.stringValue())); } else { JsonObjectBuilder objectObjBuilder = getStatementObject((Resource) object); - document.add(prefixedPredicate, objectObjBuilder); + document.add(getPrefixedAnnotation(predicate.stringValue()), objectObjBuilder); } } })); @@ -179,6 +180,7 @@ protected TDJsonWriter addGraph() { } protected JsonObjectBuilder getStatementObject(Resource subject) { + //TODO Convert to JsonArry when sub and pred are the same. JsonObjectBuilder subjectObjBuilder = Json.createObjectBuilder(); td.getGraph().ifPresent(g -> g.getStatements(subject, null, null) diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 4eeab021..7be44581 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -439,7 +439,6 @@ public void testWriteAdditionalMetadata() { BNode protocolId = rdf.createBNode(); metadata.add(rdf.createIRI("http://example.org/lamp123"), rdf.createIRI(NS,"hasManual"), manualId); metadata.add(manualId, RDF.TYPE, rdf.createIRI(NS, "Manual")); - //metadata.add(manualId, DCTERMS.TITLE, rdf.createLiteral("My Lamp Manual")); ThingDescription td = new ThingDescription.Builder("My Thing") .addThingURI("http://example.org/lamp123") @@ -447,7 +446,8 @@ public void testWriteAdditionalMetadata() { .addTriple(rdf.createIRI("http://example.org/lamp123"), RDF.TYPE, rdf.createIRI(NS, "Artifact")) .addTriple(protocolId, RDF.TYPE, rdf.createIRI(NS, "UsageProtocol")) - //.addTriple(protocolId, DCTERMS.TITLE, rdf.createLiteral("Party Light")) + //.addTriple(rdf.createIRI("http://example.org/lamp123"),rdf.createIRI(NS,"hasManual"), + // rdf.createIRI("http://example.org/manuals/anotherManual")) .addGraph(metadata) .addGraph(new ModelBuilder() .add(manualId, rdf.createIRI(NS, "hasUsageProtocol"), protocolId) @@ -468,6 +468,7 @@ public void testWriteAdditionalMetadata() { .add("@type", Json.createArrayBuilder() .add("eve:Artifact") .add("saref:LightSwitch")) + //.add("eve:hasManual", "http://example.org/manuals/anotherManual") .add("eve:hasManual" , Json.createObjectBuilder() .add("@type","eve:Manual") .add("eve:hasUsageProtocol", Json.createObjectBuilder() @@ -479,6 +480,7 @@ public void testWriteAdditionalMetadata() { .setNamespace("saref", "https://saref.etsi.org/core/") .getJson(); + System.out.println(test); Assert.assertEquals(expected, test); } From 247d3ad4e28bed08158dce1e07b7c23f303f0131 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 23 Sep 2021 09:47:07 +0200 Subject: [PATCH 25/28] Very small changes to test and cleaner code in writer --- .../wot/td/io/json/TDJsonWriter.java | 19 ++++++++++++++++--- .../wot/td/io/json/TDJsonWriterTest.java | 13 ++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index b0afd555..cfd922ed 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -189,12 +189,14 @@ protected JsonObjectBuilder getStatementObject(Resource subject) { Value object = statement.getObject(); if (!object.isBNode()) { + String key; if (predicate.equals(RDF.TYPE)) { - subjectObjBuilder.add(JWot.SEMANTIC_TYPE,getPrefixedAnnotation(object.stringValue())); + key = JWot.SEMANTIC_TYPE; } else { - subjectObjBuilder.add(getPrefixedAnnotation(predicate.stringValue()), - getPrefixedAnnotation(object.stringValue())); + key = getPrefixedAnnotation(predicate.stringValue()); } + String currentValue = getPrefixedAnnotation(object.stringValue()); + subjectObjBuilder.add(key,currentValue); } else { JsonObjectBuilder objectObjBuilder = getStatementObject((Resource) object); @@ -205,6 +207,17 @@ protected JsonObjectBuilder getStatementObject(Resource subject) { return subjectObjBuilder; } + private JsonObjectBuilder getPredicateBuilder(String key, String currentValue, JsonObject currentObj) { + JsonObjectBuilder objBuilder = Json.createObjectBuilder(); + if(currentObj.containsKey(key)){ + JsonValue previousValue = currentObj.get(key); + objBuilder.add(key, Json.createArrayBuilder().add(previousValue).add(currentValue)); + } else { + objBuilder.add(key, currentValue); + } + return objBuilder; + } + private String getPrefixedAnnotation(String annotation) { if (annotation.startsWith(TD.PREFIX)) { diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 7be44581..9ac8684c 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -447,7 +447,7 @@ public void testWriteAdditionalMetadata() { "Artifact")) .addTriple(protocolId, RDF.TYPE, rdf.createIRI(NS, "UsageProtocol")) //.addTriple(rdf.createIRI("http://example.org/lamp123"),rdf.createIRI(NS,"hasManual"), - // rdf.createIRI("http://example.org/manuals/anotherManual")) + // rdf.createIRI("http://example.org/manuals/anotherManual")) .addGraph(metadata) .addGraph(new ModelBuilder() .add(manualId, rdf.createIRI(NS, "hasUsageProtocol"), protocolId) @@ -461,19 +461,18 @@ public void testWriteAdditionalMetadata() { .add(Json.createObjectBuilder() .add("saref", "https://saref.etsi.org/core/") .add( "eve", "http://w3id.org/eve#"))) - .add("id", "http://example.org/lamp123") .add("title", THING_TITLE) - .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) - .add("security", Json.createArrayBuilder().add("nosec_sc")) + .add("id", "http://example.org/lamp123") .add("@type", Json.createArrayBuilder() .add("eve:Artifact") .add("saref:LightSwitch")) - //.add("eve:hasManual", "http://example.org/manuals/anotherManual") - .add("eve:hasManual" , Json.createObjectBuilder() + .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) + .add("security", Json.createArrayBuilder().add("nosec_sc")) + .add("eve:hasManual" , /*Json.createArrayBuilder().add("http://example.org/manuals/anotherManual").add(*/Json.createObjectBuilder() .add("@type","eve:Manual") .add("eve:hasUsageProtocol", Json.createObjectBuilder() .add("@type", "eve:UsageProtocol") - .add("eve:hasLanguage", "http://jason.sourceforge.net/wp/description/"))) + .add("eve:hasLanguage", "http://jason.sourceforge.net/wp/description/")))//) .build(); JsonObject test = new TDJsonWriter(td) From 7dc62b9439acf1f492597813bd7d9923d95fb5a2 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 23 Sep 2021 09:59:51 +0200 Subject: [PATCH 26/28] Clean code again in writer --- .../interactions/wot/td/io/json/TDJsonWriter.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index cfd922ed..7d8fcb00 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -187,21 +187,21 @@ protected JsonObjectBuilder getStatementObject(Resource subject) { .forEach(statement -> { IRI predicate = statement.getPredicate(); Value object = statement.getObject(); - + String key; + JsonValue currentValue; if (!object.isBNode()) { - String key; if (predicate.equals(RDF.TYPE)) { key = JWot.SEMANTIC_TYPE; } else { key = getPrefixedAnnotation(predicate.stringValue()); } - String currentValue = getPrefixedAnnotation(object.stringValue()); - subjectObjBuilder.add(key,currentValue); + currentValue = Json.createValue(getPrefixedAnnotation(object.stringValue())); } else { - JsonObjectBuilder objectObjBuilder = getStatementObject((Resource) object); - subjectObjBuilder.add(getPrefixedAnnotation(predicate.stringValue()), objectObjBuilder); + key = getPrefixedAnnotation(predicate.stringValue()); + currentValue = getStatementObject((Resource) object).build(); } + subjectObjBuilder.add(key,currentValue); })); return subjectObjBuilder; From 29921bc4a48ab4e921ede905dcc01a6cc2b1c038 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 23 Sep 2021 10:30:45 +0200 Subject: [PATCH 27/28] Add support for multiple predicates on the same subject --- .../wot/td/io/json/TDJsonWriter.java | 62 +++++++++++-------- .../wot/td/io/json/TDJsonWriterTest.java | 8 +-- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 7d8fcb00..5235e55a 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -7,10 +7,7 @@ import ch.unisg.ics.interactions.wot.td.affordances.PropertyAffordance; import ch.unisg.ics.interactions.wot.td.io.AbstractTDWriter; import ch.unisg.ics.interactions.wot.td.vocabularies.TD; -import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Namespace; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.*; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.model.vocabulary.RDF; @@ -28,7 +25,7 @@ */ public class TDJsonWriter extends AbstractTDWriter { - private final JsonObjectBuilder document; + private JsonObjectBuilder document; private final Map prefixMap; private Optional semanticContext; @@ -154,23 +151,33 @@ protected TDJsonWriter addActions() { @Override protected TDJsonWriter addGraph() { - // TODO the getStatementObject can be exapnded so that addGraph() calls directly : + // TODO the getStatementObject can be expanded so that addGraph() calls directly : // document.addAll(getStatementObject(thingURI); if (td.getThingURI().isPresent()) { Resource thingURI = SimpleValueFactory.getInstance().createIRI(td.getThingURI().get()); td.getGraph().ifPresent(g -> g.getStatements(thingURI, null, null) .forEach(statement -> { - if (!statement.getPredicate().equals(RDF.TYPE)) { IRI predicate = statement.getPredicate(); Value object = statement.getObject(); - + String key = getPrefixedAnnotation(predicate.stringValue()); + JsonValue currentValue; if (!object.isBNode()) { - document.add(getPrefixedAnnotation(predicate.stringValue()), getPrefixedAnnotation(object.stringValue())); + currentValue = Json.createValue(getPrefixedAnnotation(object.stringValue())); } else { - JsonObjectBuilder objectObjBuilder = getStatementObject((Resource) object); - document.add(getPrefixedAnnotation(predicate.stringValue()), objectObjBuilder); + currentValue = getStatementObject((Resource) object).build(); + } + //TODO Consider changing library for JsonBuilder because this is VERY ugly, + // it's impossible to check if keys exists without building but that erase the content of the builder itself. + JsonObject obj = document.build(); + if (obj.containsKey(key)) { + JsonValue previousValue = obj.get(key); + document = Json.createObjectBuilder(obj); + document.add(key, Json.createArrayBuilder().add(previousValue).add(currentValue)); + } else { + document = Json.createObjectBuilder(obj); + document.add(key, currentValue); } } })); @@ -182,9 +189,12 @@ protected TDJsonWriter addGraph() { protected JsonObjectBuilder getStatementObject(Resource subject) { //TODO Convert to JsonArry when sub and pred are the same. JsonObjectBuilder subjectObjBuilder = Json.createObjectBuilder(); - - td.getGraph().ifPresent(g -> g.getStatements(subject, null, null) - .forEach(statement -> { + Iterable statements = new ArrayList<>(); + if(td.getGraph().isPresent()){ + statements = td.getGraph().get().getStatements(subject, null, null); + } + for(Statement statement: statements) + { IRI predicate = statement.getPredicate(); Value object = statement.getObject(); String key; @@ -201,23 +211,21 @@ protected JsonObjectBuilder getStatementObject(Resource subject) { key = getPrefixedAnnotation(predicate.stringValue()); currentValue = getStatementObject((Resource) object).build(); } - subjectObjBuilder.add(key,currentValue); - })); + //TODO is this necessary or on the top level is enough? I think this could be useful but not sure + JsonObject obj = subjectObjBuilder.build(); + if (obj.containsKey(key)) { + JsonValue previousValue = obj.get(key); + subjectObjBuilder = Json.createObjectBuilder(obj); + subjectObjBuilder.add(key, Json.createArrayBuilder().add(previousValue).add(currentValue)); + } else { + subjectObjBuilder = Json.createObjectBuilder(obj); + subjectObjBuilder.add(key, currentValue); + } + } return subjectObjBuilder; } - private JsonObjectBuilder getPredicateBuilder(String key, String currentValue, JsonObject currentObj) { - JsonObjectBuilder objBuilder = Json.createObjectBuilder(); - if(currentObj.containsKey(key)){ - JsonValue previousValue = currentObj.get(key); - objBuilder.add(key, Json.createArrayBuilder().add(previousValue).add(currentValue)); - } else { - objBuilder.add(key, currentValue); - } - return objBuilder; - } - private String getPrefixedAnnotation(String annotation) { if (annotation.startsWith(TD.PREFIX)) { diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 9ac8684c..09d7e44e 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -446,8 +446,8 @@ public void testWriteAdditionalMetadata() { .addTriple(rdf.createIRI("http://example.org/lamp123"), RDF.TYPE, rdf.createIRI(NS, "Artifact")) .addTriple(protocolId, RDF.TYPE, rdf.createIRI(NS, "UsageProtocol")) - //.addTriple(rdf.createIRI("http://example.org/lamp123"),rdf.createIRI(NS,"hasManual"), - // rdf.createIRI("http://example.org/manuals/anotherManual")) + .addTriple(rdf.createIRI("http://example.org/lamp123"),rdf.createIRI(NS,"hasManual"), + rdf.createIRI("http://example.org/manuals/anotherManual")) .addGraph(metadata) .addGraph(new ModelBuilder() .add(manualId, rdf.createIRI(NS, "hasUsageProtocol"), protocolId) @@ -468,11 +468,11 @@ public void testWriteAdditionalMetadata() { .add("saref:LightSwitch")) .add("securityDefinitions", Json.createObjectBuilder().add("nosec_sc", Json.createObjectBuilder().add("scheme", "nosec"))) .add("security", Json.createArrayBuilder().add("nosec_sc")) - .add("eve:hasManual" , /*Json.createArrayBuilder().add("http://example.org/manuals/anotherManual").add(*/Json.createObjectBuilder() + .add("eve:hasManual" , Json.createArrayBuilder().add("http://example.org/manuals/anotherManual").add(Json.createObjectBuilder() .add("@type","eve:Manual") .add("eve:hasUsageProtocol", Json.createObjectBuilder() .add("@type", "eve:UsageProtocol") - .add("eve:hasLanguage", "http://jason.sourceforge.net/wp/description/")))//) + .add("eve:hasLanguage", "http://jason.sourceforge.net/wp/description/")))) .build(); JsonObject test = new TDJsonWriter(td) From 945f086f1296673548d2d8cf7bf22f70fca3e7c2 Mon Sep 17 00:00:00 2001 From: Samuele Date: Thu, 23 Sep 2021 10:53:42 +0200 Subject: [PATCH 28/28] Re-enable change of having the name mandatory instate of title --- .../interactions/wot/td/io/json/TDJsonWriter.java | 4 ++-- .../wot/td/io/json/TDJsonWriterTest.java | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java index 5235e55a..a4f27e4a 100644 --- a/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -254,7 +254,7 @@ private JsonObjectBuilder getAffordancesObject if (!affordances.isEmpty()) { JsonObjectBuilder rootObj = Json.createObjectBuilder(); affordances.forEach(aff -> - rootObj.add(aff.getTitle().get(), mapper.apply(aff)) + rootObj.add(aff.getName().get(), mapper.apply(aff)) ); return rootObj; } @@ -304,7 +304,7 @@ private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { } //add readable name - affordance.getName().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); + affordance.getTitle().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); //TODO description is missing in the model diff --git a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java index 09d7e44e..4a7a5043 100644 --- a/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -208,7 +208,7 @@ public void testThingWithProperties() { new Form.Builder(THING_IRI + "/status") .setMethodName("GET") .addOperationType(TD.readProperty).build() - ).addObserve().addTitle("status").build()); + ).addObserve().addName("status").build()); ThingDescription td = new ThingDescription.Builder(THING_TITLE) .addBaseURI(IO_BASE_IRI) @@ -249,7 +249,7 @@ public void testThingPropertiesWithOneSemanticType() { new StringSchema.Builder().build(), new Form.Builder(THING_IRI + "/status").build()) .addObserve() - .addTitle("status") + .addName("status") .addSemanticType("http://example.org/Status") .build()); @@ -298,7 +298,7 @@ public void testThingWithActions() { actions.add(new ActionAffordance.Builder( new Form.Builder(THING_IRI + "/changeColor") .setMethodName("POST").build() - ).addTitle("changeColor") + ).addName("changeColor") .addInputSchema(new ObjectSchema.Builder() .addProperty("color", new StringSchema.Builder().build()) .build()) @@ -309,7 +309,7 @@ public void testThingWithActions() { actions.add(new ActionAffordance.Builder( new Form.Builder(THING_IRI + "/changeState") .setMethodName("POST").build() - ).addTitle("changeState").build() + ).addName("changeState").build() ); @@ -363,13 +363,13 @@ public void testThingActionsWithSemanticTypes() { actions.add(new ActionAffordance.Builder( new Form.Builder(THING_IRI + "/changeColor") .build()) - .addTitle("changeColor") + .addName("changeColor") .addSemanticType("http://example.org/1/SetColor1") .addSemanticType("http://example.org/2/SetColor2") .build()); actions.add(new ActionAffordance.Builder( new Form.Builder(THING_IRI + "/changeState").build()) - .addTitle("changeState") + .addName("changeState") .addSemanticType("http://example.org/1/SetState1") .addSemanticType("http://example.org/2/SetState2") .build());