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/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/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() { 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/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/JWot.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java new file mode 100644 index 00000000..910be39a --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/JWot.java @@ -0,0 +1,53 @@ +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"; + + + public static final String BASE = "base"; + public static final String CONTEXT = "@context"; + + public static final String TITLE = "title"; + public static final String DESCRIPTION = "description"; + + public static final String PROPERTIES = "properties"; + public static final String OBSERVABLE = "observable"; + + 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"; + + public static final String EVENTS = "events"; + + public static final String SEMANTIC_TYPE = "@type"; + public static final String TYPE = "type"; + + 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"; + + 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/SchemaJsonWriter.java b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java new file mode 100644 index 00000000..3a754483 --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriter.java @@ -0,0 +1,96 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +import ch.unisg.ics.interactions.wot.td.schemas.*; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; + +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. + * @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: + return getObjectSchema((ObjectSchema) schema); + case DataSchema.ARRAY: + return getArraySchema((ArraySchema) schema); + case DataSchema.BOOLEAN: + return getSimpleSchema(schema, DataSchema.BOOLEAN); + case DataSchema.INTEGER: + return getIntegerSchema((IntegerSchema) schema); + case DataSchema.NUMBER: + return getNumberSchema((NumberSchema) schema); + case DataSchema.STRING: + return getSimpleSchema(schema, DataSchema.STRING); + case DataSchema.NULL: + return getSimpleSchema(schema, DataSchema.NULL); + default: + return Json.createObjectBuilder(); + } + } + + 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) { + 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) { + 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(schema, DataSchema.ARRAY); + schema.getMinItems().ifPresent(min -> schemaObj.add("minItems", min)); + schema.getMaxItems().ifPresent(max -> schemaObj.add("maxItems", max)); + + 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(schema, DataSchema.OBJECT); + + JsonObjectBuilder propObj = Json.createObjectBuilder(); + schema.getProperties().forEach((k,v) -> propObj.add(k, getDataSchema(v))); + schemaObj.add("properties", propObj); + + 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 new file mode 100644 index 00000000..a4f27e4a --- /dev/null +++ b/src/main/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriter.java @@ -0,0 +1,346 @@ +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 ch.unisg.ics.interactions.wot.td.vocabularies.TD; +import org.eclipse.rdf4j.model.*; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; + +import javax.json.*; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.Comparator.comparing; + +/** + * A writer to serialize TDs in the JSON-LD 1.1 format. + */ +public class TDJsonWriter extends AbstractTDWriter { + + private JsonObjectBuilder document; + private final Map prefixMap; + private Optional semanticContext; + + public TDJsonWriter(ThingDescription td) { + super(td); + document = Json.createObjectBuilder(); + semanticContext = Optional.empty(); + prefixMap = new HashMap<>(); + } + + 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) + .add(semanticContext.get())); + } else { + document.add(JWot.CONTEXT, JWot.WOT_CONTEXT); + } + + this.addTitle() + .addTypes() + .addSecurity() + .addBaseURI() + .addProperties() + .addActions() + .addGraph(); + return document.build(); + } + + @Override + public String write() { + OutputStream out = new ByteArrayOutputStream(); + JsonWriter writer = Json.createWriter(out); + writer.write(this.getJson()); + return out.toString(); + } + + @Override + public TDJsonWriter setNamespace(String prefix, String namespace) { + this.prefixMap.put(namespace, prefix); + if (semanticContext.isPresent()) { + semanticContext.get().add(prefix, namespace); + } else { + JsonObjectBuilder semContextObj = Json.createObjectBuilder() + .add(prefix, namespace); + semanticContext = Optional.of(semContextObj); + } + + return this; + } + + @Override + protected TDJsonWriter addTypes() { + //TODO This is ugly why is the types sometimes a set and sometimes a list? + + 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(semanticTypes.stream().findFirst().orElse(""))); + } + 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_sc")); + return this; + } + + @Override + protected TDJsonWriter addBaseURI() { + td.getBaseURI().ifPresent(uri -> document.add(JWot.BASE, uri)); + return this; + } + + @Override + protected TDJsonWriter addProperties() { + if (!td.getProperties().isEmpty()) { + document.add(JWot.PROPERTIES, this.getAffordancesObject(td.getProperties(), this::getProperty)); + } + return this; + } + + @Override + protected TDJsonWriter addActions() { + if (!td.getActions().isEmpty()) { + document.add(JWot.ACTIONS, this.getAffordancesObject(td.getActions(), this::getAction)); + } + return this; + } + + @Override + protected TDJsonWriter addGraph() { + // 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()) { + currentValue = Json.createValue(getPrefixedAnnotation(object.stringValue())); + } + else { + 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); + } + } + })); + } + + return this; + } + + protected JsonObjectBuilder getStatementObject(Resource subject) { + //TODO Convert to JsonArry when sub and pred are the same. + JsonObjectBuilder subjectObjBuilder = Json.createObjectBuilder(); + 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; + JsonValue currentValue; + if (!object.isBNode()) { + if (predicate.equals(RDF.TYPE)) { + key = JWot.SEMANTIC_TYPE; + } else { + key = getPrefixedAnnotation(predicate.stringValue()); + } + currentValue = Json.createValue(getPrefixedAnnotation(object.stringValue())); + } + else { + key = getPrefixedAnnotation(predicate.stringValue()); + currentValue = getStatementObject((Resource) object).build(); + } + //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 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())) + .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + + if (matchedPref.isEmpty()) { + return 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() + ":"); + } + } + + private JsonObjectBuilder getAffordancesObject(List affordances, Function mapper) { + if (!affordances.isEmpty()) { + JsonObjectBuilder rootObj = Json.createObjectBuilder(); + affordances.forEach(aff -> + rootObj.add(aff.getName().get(), mapper.apply(aff)) + ); + return rootObj; + } + return Json.createObjectBuilder(); //empty + } + + private JsonObjectBuilder getProperty(PropertyAffordance prop) { + JsonObjectBuilder propertyObj = getAffordance(prop) + .add(JWot.OBSERVABLE, prop.isObservable()); + JsonObjectBuilder dataSchema = SchemaJsonWriter.getDataSchema(prop.getDataSchema()); + propertyObj.addAll(dataSchema); + return propertyObj; + } + + private JsonObjectBuilder getAction(ActionAffordance action) { + JsonObjectBuilder actionObj = getAffordance(action); + + //TODO safe and idempotent are missing in the model + + action.getInputSchema().ifPresent(d -> + actionObj.add(JWot.INPUT, SchemaJsonWriter.getDataSchema(d)) + ); + action.getOutputSchema().ifPresent(d -> + actionObj.add(JWot.OUTPUT, SchemaJsonWriter.getDataSchema(d)) + ); + + return actionObj; + } + + + private JsonArrayBuilder getSemanticTypes(List semanticTypes) { + JsonArrayBuilder types = Json.createArrayBuilder(); + semanticTypes.forEach(t -> types.add(getPrefixedAnnotation(t))); + return types; + } + + private JsonObjectBuilder getAffordance(InteractionAffordance affordance) { + JsonObjectBuilder affordanceObj = Json.createObjectBuilder(); + + //add semantic type(s) + 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 + affordance.getTitle().ifPresent(n -> affordanceObj.add(JWot.TITLE, n)); + + //TODO description is missing in the model + + //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(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 + form.getMethodName().ifPresent(m -> { + if (form.getOperationTypes().size() == 1) + formObj.add(JWot.METHOD, m); + }); + formArray.add(formObj); + }); + return formArray; + } +} 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); } } 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/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/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/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 61% 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..214ed5a6 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,24 +1,11 @@ -package ch.unisg.ics.interactions.wot.td.io; - -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 org.eclipse.rdf4j.rio.RDFFormat; -import org.junit.Test; +package ch.unisg.ics.interactions.wot.td.io.graph; 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; @@ -28,536 +15,545 @@ 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" + "@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" + - " ] ." ; - - 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" + - "} ]"; - + " ]\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 td: .\n" + "@prefix htv: .\n" + "@prefix hctl: .\n" + "@prefix dct: .\n" + "@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 = " ] ." ; - + + 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 -> - scheme.getSchemeType().equals(WoTSec.NoSecurityScheme))); + + assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> + scheme.getSchemeType().equals(WoTSec.NoSecurityScheme))); } - + @Test public void testReadAPIKeySecurityScheme() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@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(WoTSec.APIKeySecurityScheme, scheme.getSchemeType()); assertEquals(TokenLocation.HEADER, ((APIKeySecurityScheme) scheme).getIn()); assertEquals("X-API-Key", ((APIKeySecurityScheme) scheme).getName().get()); } - + @Test public void testAPIKeySecuritySchemeDefaultValues() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@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(); - assertEquals(WoTSec.APIKeySecurityScheme, ((APIKeySecurityScheme) scheme).getSchemeType()); + assertEquals(WoTSec.APIKeySecurityScheme, scheme.getSchemeType()); assertEquals(TokenLocation.QUERY, ((APIKeySecurityScheme) scheme).getIn()); assertFalse(((APIKeySecurityScheme) scheme).getName().isPresent()); } - + @Test(expected = InvalidTDException.class) public void testAPIKeySecuritySchemeInvalidTokenLocation() { String testTD = - "@prefix td: .\n" + + "@prefix td: .\n" + "@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 = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix htv: .\n" + "@prefix wotsec: .\n" + "@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))); + .equals(WoTSec.NoSecurityScheme))); assertTrue(reader.readSecuritySchemes().stream().anyMatch(scheme -> scheme.getSchemeType() - .equals(WoTSec.APIKeySecurityScheme))); + .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)); - + assertTrue(action.getSemanticTypes().contains(TD.ActionAffordance)); + 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 = - "@prefix td: .\n" + + "@prefix td: .\n" + "@prefix htv: .\n" + "@prefix hctl: .\n" + "@prefix dct: .\n" + "@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()); - + .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 - + TEST_IO_TAIL); - + + 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()); + // 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()); 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 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" + - " 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)); + + assertTrue(actualMessage.contains(expectedMessage)); } - - private void assertForm(Form form, String methodName, String target, - String contentType, String operationType) { + + 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..65bd78bd 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,12 +1,16 @@ -package ch.unisg.ics.interactions.wot.td.io; - -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +package ch.unisg.ics.interactions.wot.td.io.graph; +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; @@ -21,249 +25,245 @@ 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"; 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)), - new ArrayList(Arrays.asList())); - + + ThingDescription td = constructThingDescription(new ArrayList<>(Collections.singletonList(property)), + new ArrayList<>(Collections.emptyList())); + 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 +280,19 @@ public void testWriteOneAction() throws RDFParseException, RDFHandlerException, .addRequiredProperties("boolean_value") .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); } - + @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 +301,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 +322,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 +365,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 +389,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(); } } 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..fbf0e3eb --- /dev/null +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/SchemaJsonWriterTest.java @@ -0,0 +1,171 @@ +package ch.unisg.ics.interactions.wot.td.io.json; + +import ch.unisg.ics.interactions.wot.td.schemas.*; +import org.junit.Assert; +import org.junit.Test; + +import javax.json.Json; +import javax.json.JsonObject; + +public class SchemaJsonWriterTest { + + @Test + public void testWriteStringSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "string") + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new StringSchema.Builder().build() + ).build(); + Assert.assertEquals(expected, test); + } + + @Test + public void testBooleanSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "boolean") + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new BooleanSchema.Builder().build() + ).build(); + Assert.assertEquals(expected, test); + } + + @Test + public void testNullSchema() { + JsonObject expected = Json.createObjectBuilder() + .add("type", "null") + .build(); + JsonObject test = SchemaJsonWriter.getDataSchema( + new NullSchema.Builder().build() + ).build(); + Assert.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(); + Assert.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(); + Assert.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(); + + Assert.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(); + Assert.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("minimum", 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(); + + Assert.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(); + + 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 new file mode 100644 index 00000000..4a7a5043 --- /dev/null +++ b/src/test/java/ch/unisg/ics/interactions/wot/td/io/json/TDJsonWriterTest.java @@ -0,0 +1,486 @@ +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.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 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; + +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 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) + .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 testThingWithSemanticTypes() { + ThingDescription td = new ThingDescription.Builder(THING_TITLE) + .addSemanticType("http://w3id.org/eve#Artifact") + .addSemanticType("http://iotschema.org/Light") + .addSecurityScheme(new NoSecurityScheme()) + .build(); + + JsonObject expected = Json.createObjectBuilder() + .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/"))) + .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) + .setNamespace("eve", "http://w3id.org/eve#") + .setNamespace("iot", "http://iotschema.org/") + .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("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("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")) + .build(); + + 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); + } + + @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); + } + + @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(TD.readProperty).build() + ).addObserve().addName("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); + } + + @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() + .addName("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() { + List actions = new ArrayList<>(); + actions.add(new ActionAffordance.Builder( + new Form.Builder(THING_IRI + "/changeColor") + .setMethodName("POST").build() + ).addName("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() + ).addName("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("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("invokeaction")) + ) + ) + ) + ) + .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()) + .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()) + .addName("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("contentType", "application/json") + .add("htv:methodName", "POST") + .add("op", Json.createArrayBuilder().add("invokeaction")) + ) + ) + ) + ) + .build(); + + JsonObject test = new TDJsonWriter(td) + .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")); + + 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(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) + .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("title", THING_TITLE) + .add("id", "http://example.org/lamp123") + .add("@type", Json.createArrayBuilder() + .add("eve:Artifact") + .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("@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(); + + System.out.println(test); + Assert.assertEquals(expected, test); + } + +}