Sfoglia il codice sorgente

Next finalized version allowing observation uploading

Lukas Cerny 4 anni fa
parent
commit
7650fa1442
60 ha cambiato i file con 2536 aggiunte e 928 eliminazioni
  1. 26 0
      .gitlab-ci.yml
  2. 33 0
      Dockerfile
  3. 3 3
      README.md
  4. 37 14
      build.gradle
  5. 3 1
      connector-core/src/main/java/io/connector/core/AddressPath.java
  6. 2 2
      connector-core/src/main/java/io/connector/core/VertxHttpServer.java
  7. 2 2
      connector-core/src/main/java/io/connector/core/http/HttpContentType.java
  8. 2 2
      connector-core/src/main/java/io/connector/core/http/HttpHeader.java
  9. 9 1
      connector-core/src/main/java/io/connector/core/http/RequestUriComponent.java
  10. 60 3
      connector-core/src/main/java/io/connector/core/http/RequestUriParser.java
  11. 43 0
      connector-core/src/main/java/io/connector/core/util/Tuple.java
  12. 8 0
      connector-core/src/main/java/io/connector/test/api/TestType.java
  13. 41 41
      connector-model/src/main/java/io/connector/model/afarcloud/Location.java
  14. 36 0
      connector-model/src/main/java/io/connector/model/afarcloud/MultiSensor.java
  15. 43 43
      connector-model/src/main/java/io/connector/model/afarcloud/MultiSimpleObservation.java
  16. 122 122
      connector-model/src/main/java/io/connector/model/afarcloud/Observation.java
  17. 34 34
      connector-model/src/main/java/io/connector/model/afarcloud/ResourceMeasurement.java
  18. 64 64
      connector-model/src/main/java/io/connector/model/afarcloud/ResponseModel.java
  19. 42 42
      connector-model/src/main/java/io/connector/model/afarcloud/SensorTelemetry.java
  20. 69 67
      connector-model/src/main/java/io/connector/model/afarcloud/SimpleObservation.java
  21. 0 28
      connector-model/src/main/java/io/connector/model/sensorthings/Datastream.java
  22. 95 0
      connector-model/src/main/java/io/connector/model/sensorthings/DatastreamInsert.java
  23. 90 90
      connector-model/src/main/java/io/connector/model/sensorthings/FeatureOfInterest.java
  24. 1 1
      connector-model/src/main/java/io/connector/model/sensorthings/Filter.java
  25. 15 1
      connector-model/src/main/java/io/connector/model/sensorthings/Geometry.java
  26. 55 55
      connector-model/src/main/java/io/connector/model/sensorthings/HistoricalLocation.java
  27. 43 25
      connector-model/src/main/java/io/connector/model/sensorthings/Location.java
  28. 29 0
      connector-model/src/main/java/io/connector/model/sensorthings/LocationInfo.java
  29. 71 71
      connector-model/src/main/java/io/connector/model/sensorthings/Observation.java
  30. 53 0
      connector-model/src/main/java/io/connector/model/sensorthings/ObservationInsert.java
  31. 7 0
      connector-model/src/main/java/io/connector/model/sensorthings/ObservedProperty.java
  32. 9 0
      connector-model/src/main/java/io/connector/model/sensorthings/Params.java
  33. 71 71
      connector-model/src/main/java/io/connector/model/sensorthings/Sensor.java
  34. 48 0
      connector-model/src/main/java/io/connector/model/sensorthings/SensorInsert.java
  35. 80 80
      connector-model/src/main/java/io/connector/model/sensorthings/Thing.java
  36. 70 0
      connector-model/src/main/java/io/connector/model/sensorthings/ThingInsert.java
  37. 40 0
      connector-model/src/main/java/io/connector/model/sensorthings/UnitOfMeasurement.java
  38. 71 5
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCHttpClient.java
  39. 254 32
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java
  40. 312 0
      connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsAssertions.java
  41. 94 0
      connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsOfflineIntegrationTest.java
  42. 320 0
      connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsOnlineIntegrationTest.java
  43. 1 0
      connector-module-afarcloud/src/test/resources/afc/getAllSensors.json
  44. 1 0
      connector-module-afarcloud/src/test/resources/afc/getObservationsBySensor.json
  45. 1 0
      connector-module-afarcloud/src/test/resources/afc/getSensor.json
  46. 0 0
      connector-module-afarcloud/src/test/resources/ogc/datastreams.json
  47. 1 0
      connector-module-afarcloud/src/test/resources/ogc/historicalLocations.json
  48. 1 0
      connector-module-afarcloud/src/test/resources/ogc/locations.json
  49. 1 0
      connector-module-afarcloud/src/test/resources/ogc/observationProperty.json
  50. 1 0
      connector-module-afarcloud/src/test/resources/ogc/observations.json
  51. 1 0
      connector-module-afarcloud/src/test/resources/ogc/sensor.json
  52. 1 0
      connector-module-afarcloud/src/test/resources/ogc/things.json
  53. BIN
      doc/OGCSensorThings_v1.1_navigationLinks.png
  54. 16 0
      docker-compose.yaml
  55. 0 14
      docker/Dockerfile
  56. 0 7
      docker/start.sh
  57. BIN
      gradle/wrapper/gradle-6.5.1-bin.zip
  58. BIN
      gradle/wrapper/gradle-wrapper.jar
  59. 3 3
      gradle/wrapper/gradle-wrapper.properties
  60. 1 4
      settings.gradle

+ 26 - 0
.gitlab-ci.yml

@@ -0,0 +1,26 @@
+image: docker:20.10.5
+
+services:
+  - docker:20.10.5-dind
+
+before_script:
+  - docker info
+
+stages:
+  - build
+  - test
+
+build:
+  stage: build
+  script:
+    - docker build --target builder .
+
+unitTest:
+  stage: test
+  script:
+    - docker build --target unitTest .
+
+integrationTest:
+  stage: test
+  script:
+    - docker build --target integrationTest .

+ 33 - 0
Dockerfile

@@ -0,0 +1,33 @@
+FROM adoptopenjdk/openjdk11:jdk-11.0.10_9-debian AS builder
+
+ARG config_file
+ARG module
+
+COPY src /app/src
+COPY connector-app /app/connector-app
+COPY connector-core /app/connector-core
+COPY connector-model /app/connector-model
+COPY $module /app/$module
+COPY $config_file /app/config.yaml
+COPY gradle /app/gradle
+COPY build.gradle settings.gradle gradlew /app/
+WORKDIR /app
+RUN ./gradlew assemble
+
+FROM adoptopenjdk/openjdk11:jdk-11.0.10_9-debian AS unitTest
+COPY . /app/
+WORKDIR /app
+RUN ./gradlew unitTest
+
+FROM adoptopenjdk/openjdk11:jdk-11.0.10_9-debian AS integrationTest
+COPY . /app/
+WORKDIR /app
+RUN ./gradlew integrationTest
+
+FROM adoptopenjdk/openjdk11:jre-11.0.10_9-alpine AS production
+ENV port 8080
+COPY --from=builder /app/bin/libs /app/bin
+COPY --from=builder /app/config.yaml /app/
+WORKDIR /app
+EXPOSE $port
+CMD java -cp "bin/*" io.connector.Main -cf config.yaml -p $port

+ 3 - 3
README.md

@@ -13,7 +13,7 @@ This software was created as a part of the ECSEL JU project 783221 Aggregate FAR
 
 Requirements: *Gradle*
 
-`./gradle build`
+`./gradlew build`
 
 ### Run
 
@@ -23,11 +23,11 @@ Requirements: *Java 8+*
 
 ### Build docker image
 
-`docker build -t <image_custom_name> --build-arg config_file=<yaml_file> --build-arg port=<server_port> -f ./docker/Dockerfile .`
+`docker compose build`
 
 ### Run docker image
 
-`docker run --name <container_name> -p <host_port>:<server_port> <image>`
+`docker compose up`
 
 ### Usage
 

+ 37 - 14
build.gradle

@@ -13,6 +13,19 @@ dependencies {
     compile project(":connector-app")
 }
 
+// create fat JAR
+jar {
+    manifest {
+        attributes "Main-Class": "$mainClassName"
+    }
+
+    from {
+        configurations.runtimeClasspath.collect {
+            it.isDirectory() ? it : zipTree(it)
+        }
+    }
+}
+
 // setting for all projects
 allprojects {
 
@@ -35,18 +48,15 @@ allprojects {
     dependencies {
         compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.12.0'
         compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.12.0'
+
+        testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.0-M1'
     }
 
-    // create fat JAR
-    jar {
-        manifest {
-            attributes "Main-Class": "$mainClassName"
-        }
+    task unitTest(type: Test) {
+        description = 'Runs JUnit tests.'
 
-        from {
-            configurations.runtimeClasspath.collect {
-                it.isDirectory() ? it : zipTree(it)
-            }
+        useJUnitPlatform {
+            excludeTags 'integration'
         }
     }
 }
@@ -66,17 +76,20 @@ project("connector-app") {
 project(":connector-core") {
 
     dependencies {
-        compile group: 'io.vertx', name: 'vertx-core', version: '3.9.1'
-        compile group: 'io.vertx', name: 'vertx-web', version: '3.9.1'
+//        compile group: 'io.vertx', name: 'vertx-core', version: '4.0.3'
+        compile group: 'io.vertx', name: 'vertx-web', version: '4.0.3'
         compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.9'
         compile group: 'org.yaml', name: 'snakeyaml', version: '1.24'
+
+        compile group: 'io.vertx', name: 'vertx-junit5', version: '4.0.3'
+        compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.3'
     }
 }
 
 project(":connector-model") {
 
     dependencies {
-        compile group: 'io.vertx', name: 'vertx-core', version: '3.9.1'
+        compile group: 'io.vertx', name: 'vertx-core', version: '4.0.3'
     }
 }
 
@@ -86,7 +99,17 @@ configure(moduleNames) {
     dependencies {
         compile project(":connector-core")
         compile project(":connector-model")
+    }
 
-        compile group: 'io.vertx', name: 'vertx-core', version: '3.9.1'
+    task integrationTest(type: Test) {
+        description = 'Runs integration tests for ' + project.name
+
+        useJUnitPlatform {
+            includeTags 'integration'
+        }
+
+        filter {
+            includeTestsMatching "io." +project.name.replace("-", ".") + ".*"
+        }
     }
-}
+}

+ 3 - 1
connector-core/src/main/java/io/connector/core/AddressPath.java

@@ -18,7 +18,9 @@ public final class AddressPath {
         public static String create(String ...parts) {
             StringBuilder builder = new StringBuilder();
             for (String part : parts) {
-                builder.append(part.charAt(0) == DELIMITER ? part : DELIMITER + part);
+                if (!part.isEmpty()) {
+                    builder.append(part.charAt(0) == DELIMITER ? part : DELIMITER + part);
+                }
             }
             return builder.toString();
         }

+ 2 - 2
connector-core/src/main/java/io/connector/core/VertxHttpServer.java

@@ -20,8 +20,8 @@ import java.time.OffsetDateTime;
 import java.util.List;
 
 import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
-import static io.connector.core.http.ContentType.APPLICATION_JSON;
-import static io.connector.core.http.Header.CONTENT_TYPE;
+import static io.connector.core.http.HttpContentType.APPLICATION_JSON;
+import static io.connector.core.http.HttpHeader.CONTENT_TYPE;
 import static io.connector.core.AddressPath.Creator.create;
 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 

+ 2 - 2
connector-core/src/main/java/io/connector/core/http/ContentType.java → connector-core/src/main/java/io/connector/core/http/HttpContentType.java

@@ -9,7 +9,7 @@ package io.connector.core.http;
  * @version 1.0
  * @since 1.0
  */
-public final class ContentType {
+public final class HttpContentType {
 
     public static final String APPLICATION_ATOM_XML = "application/atom+xml";
     public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
@@ -24,6 +24,6 @@ public final class ContentType {
     public static final String TEXT_XML = "text/xml";
     public static final String WILDCARD = "*/*";
 
-    private ContentType() {}
+    private HttpContentType() {}
 
 }

+ 2 - 2
connector-core/src/main/java/io/connector/core/http/Header.java → connector-core/src/main/java/io/connector/core/http/HttpHeader.java

@@ -9,12 +9,12 @@ package io.connector.core.http;
  * @version 1.0
  * @since 1.0
  */
-public final class Header {
+public final class HttpHeader {
 
     public static final String AUTHORIZATION = "Authorization";
     public static final String DATE = "Date";
     public static final String ACCEPT = "Accept";
     public static final String CONTENT_TYPE = "Content-Type";
 
-    private Header() { }
+    private HttpHeader() { }
 }

+ 9 - 1
connector-core/src/main/java/io/connector/core/http/RequestUriComponent.java

@@ -3,6 +3,8 @@
 
 package io.connector.core.http;
 
+import java.util.Map;
+
 import static io.connector.core.AddressPath.Creator.create;
 
 /**
@@ -18,13 +20,15 @@ public class RequestUriComponent {
     private final String module;
     private final String gateway;
     private final String address;
+    private final Map<String, String> params;
 
-    RequestUriComponent(String domain, String prefix, String module, String gateway, String address) {
+    RequestUriComponent(String domain, String prefix, String module, String gateway, String address, Map<String, String> params) {
         this.domain = domain;
         this.prefix = prefix;
         this.module = module;
         this.gateway = gateway;
         this.address = address;
+        this.params = params;
     }
 
     public String getDomain() {
@@ -62,4 +66,8 @@ public class RequestUriComponent {
     public String getGatewayPath() {
         return create(prefix, module, gateway);
     }
+
+    public Map<String, String> getParams() {
+        return params;
+    }
 }

+ 60 - 3
connector-core/src/main/java/io/connector/core/http/RequestUriParser.java

@@ -5,6 +5,10 @@ package io.connector.core.http;
 
 import io.vertx.core.http.HttpServerRequest;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import static io.connector.core.AddressPath.Creator.create;
 
 /**
@@ -15,11 +19,64 @@ import static io.connector.core.AddressPath.Creator.create;
  */
 public final class RequestUriParser {
 
+    public static RequestUriComponent parse(String absolureURI, String moduleId, String gatewayId) {
+        if (absolureURI.isEmpty()) return null;
+        Map<String, String> params;
+        if (absolureURI.contains("?")) {
+            String [] cmp = absolureURI.split("\\?");
+            params = parseParams(cmp[1]);
+            absolureURI = cmp[0];
+        } else {
+            params = Collections.emptyMap();
+        }
+
+        String [] protocolCmp = absolureURI.split("://");
+        String [] pathCmp = protocolCmp[1].split("/");
+        String domain = String.format("%s://%s", protocolCmp[0], pathCmp[0]);
+        StringBuilder apiPrefixBuilder = new StringBuilder();
+        int pathIndex;
+        for (pathIndex = 1; pathIndex < pathCmp.length; pathIndex++) {
+            String currentPath = pathCmp[pathIndex];
+            if (currentPath.equals(moduleId)) break;
+            apiPrefixBuilder.append("/").append(currentPath);
+        }
+
+        boolean isGatewayMultiPart = gatewayId.contains("/");
+        String [] gatewayParts = isGatewayMultiPart ? gatewayId.split("/") : new String[] {gatewayId};
+        StringBuilder addressBuilder = new StringBuilder();
+        for (pathIndex += 1; pathIndex < pathCmp.length; pathIndex++) {
+            String currentPath = pathCmp[pathIndex];
+            if (currentPath.equals(gatewayParts[0])) {
+                for (int i = 1; i < gatewayParts.length; i++) {
+                    int pathIndexTmp = pathIndex + i;
+                    if (pathIndexTmp < pathCmp.length && pathCmp[pathIndexTmp].equals(gatewayParts[i])) {
+                        pathIndex++;
+                    }
+                }
+            } else {
+                addressBuilder.append("/").append(pathCmp[pathIndex]);
+            }
+        }
+
+        return new RequestUriComponent(domain, apiPrefixBuilder.toString(), moduleId, gatewayId, addressBuilder.toString(), params);
+    }
+
     public static RequestUriComponent parse(HttpServerRequest req, String moduleId, String gatewayId) {
-        String params = req.uri().substring(req.uri().indexOf("?")+1);
-        String domain = req.absoluteURI().replace(req.path(), "").replaceAll("\\?"+params, "");
+        String paths = req.uri().substring(req.uri().indexOf("?")+1);
+        String domain = req.absoluteURI().replace(req.path(), "").replaceAll("\\?"+paths, "");
         String prefix = req.path().substring(1, req.path().indexOf(moduleId)-1);
         String address = req.path().replace(create(prefix, moduleId, gatewayId), "").substring(1);
-        return new RequestUriComponent(domain, prefix, moduleId, gatewayId, address);
+        return new RequestUriComponent(domain, prefix, moduleId, gatewayId, address, Collections.emptyMap());
+    }
+
+    private static Map<String, String> parseParams(String params) {
+        if (params.isEmpty()) { return Collections.emptyMap(); }
+        String [] paramsCmp = params.split("&");
+        Map<String, String> paramsMap = new HashMap<>(paramsCmp.length);
+        for (String param : paramsCmp) {
+            String [] cmp = param.split("=");
+            paramsMap.put(cmp[0], cmp[1]);
+        }
+        return paramsMap;
     }
 }

+ 43 - 0
connector-core/src/main/java/io/connector/core/util/Tuple.java

@@ -0,0 +1,43 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.core.util;
+
+import java.util.Objects;
+
+public class Tuple<A, B> {
+    private final A item1;
+    private final B item2;
+
+    public static <A, B> Tuple<A, B> of(A item1, B item2) {
+        return new Tuple<>(item1, item2);
+    }
+
+    private Tuple(A item1, B item2) {
+        this.item1 = item1;
+        this.item2 = item2;
+    }
+
+    public A getItem1() {
+        return this.item1;
+    }
+
+    public B getItem2() {
+        return this.item2;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        } else if (o != null && this.getClass() == o.getClass()) {
+            Tuple<?, ?> tuple = (Tuple<?, ?>)o;
+            return this.item1.equals(tuple.item1) && this.item2.equals(tuple.item2);
+        } else {
+            return false;
+        }
+    }
+
+    public int hashCode() {
+        return Objects.hash(this.item1, this.item2);
+    }
+}

+ 8 - 0
connector-core/src/main/java/io/connector/test/api/TestType.java

@@ -0,0 +1,8 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.test.api;
+
+public final class TestType {
+    public static final String INTEGRATION = "integration";
+}

+ 41 - 41
connector-model/src/main/java/io/connector/model/afarcloud/Location.java

@@ -1,41 +1,41 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.afarcloud;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Location {
-
-    private Double latitude;
-    private Double longitude;
-    private Double altitude;
-
-    public Double getLatitude() {
-        return latitude;
-    }
-
-    public void setLatitude(Double latitude) {
-        this.latitude = latitude;
-    }
-
-    public Double getLongitude() {
-        return longitude;
-    }
-
-    public void setLongitude(Double longitude) {
-        this.longitude = longitude;
-    }
-
-    public Double getAltitude() {
-        return altitude;
-    }
-
-    public void setAltitude(Double altitude) {
-        this.altitude = altitude;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.afarcloud;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Location {
+
+    private Double latitude;
+    private Double longitude;
+    private Double altitude;
+
+    public Double getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(Double latitude) {
+        this.latitude = latitude;
+    }
+
+    public Double getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(Double longitude) {
+        this.longitude = longitude;
+    }
+
+    public Double getAltitude() {
+        return altitude;
+    }
+
+    public void setAltitude(Double altitude) {
+        this.altitude = altitude;
+    }
+}

+ 36 - 0
connector-model/src/main/java/io/connector/model/afarcloud/MultiSensor.java

@@ -22,6 +22,10 @@ public class MultiSensor {
     private Boolean preprocessing;
     private String pythonScript;
     private List<SensorSchema> observations;
+    private String supportedProtocol;
+    private String hardwareVersion;
+    private String softwareVersion;
+    private String firmwareVersion;
 
     public String getResourceId() {
         return resourceId;
@@ -95,6 +99,38 @@ public class MultiSensor {
         this.observations = observations;
     }
 
+    public String getSupportedProtocol() {
+        return supportedProtocol;
+    }
+
+    public void setSupportedProtocol(String supportedProtocol) {
+        this.supportedProtocol = supportedProtocol;
+    }
+
+    public String getHardwareVersion() {
+        return hardwareVersion;
+    }
+
+    public void setHardwareVersion(String hardwareVersion) {
+        this.hardwareVersion = hardwareVersion;
+    }
+
+    public String getSoftwareVersion() {
+        return softwareVersion;
+    }
+
+    public void setSoftwareVersion(String softwareVersion) {
+        this.softwareVersion = softwareVersion;
+    }
+
+    public String getFirmwareVersion() {
+        return firmwareVersion;
+    }
+
+    public void setFirmwareVersion(String firmwareVersion) {
+        this.firmwareVersion = firmwareVersion;
+    }
+
     public static class SensorSchema {
         private String observedProperty;
         private String uom;

+ 43 - 43
connector-model/src/main/java/io/connector/model/afarcloud/MultiSimpleObservation.java

@@ -1,43 +1,43 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.afarcloud;
-
-import java.util.List;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class MultiSimpleObservation {
-
-    private String resourceId;
-    private Location location;
-    private List<SimpleObservation> observations;
-
-    public String getResourceId() {
-        return resourceId;
-    }
-
-    public void setResourceId(String resourceId) {
-        this.resourceId = resourceId;
-    }
-
-    public Location getLocation() {
-        return location;
-    }
-
-    public void setLocation(Location location) {
-        this.location = location;
-    }
-
-    public List<SimpleObservation> getObservations() {
-        return observations;
-    }
-
-    public void setObservations(List<SimpleObservation> observations) {
-        this.observations = observations;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.afarcloud;
+
+import java.util.List;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class MultiSimpleObservation {
+
+    private String resourceId;
+    private Location location;
+    private List<SimpleObservation> observations;
+
+    public String getResourceId() {
+        return resourceId;
+    }
+
+    public void setResourceId(String resourceId) {
+        this.resourceId = resourceId;
+    }
+
+    public Location getLocation() {
+        return location;
+    }
+
+    public void setLocation(Location location) {
+        this.location = location;
+    }
+
+    public List<SimpleObservation> getObservations() {
+        return observations;
+    }
+
+    public void setObservations(List<SimpleObservation> observations) {
+        this.observations = observations;
+    }
+}

+ 122 - 122
connector-model/src/main/java/io/connector/model/afarcloud/Observation.java

@@ -1,122 +1,122 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.afarcloud;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Observation {
-
-    private String time;
-    private Double altitude;
-    private Double latitude;
-    private Double longitude;
-    private String geohash;
-    private Double value;
-    private String entityName;
-    private String sequenceNumber;
-    private String service;
-    private String provider;
-    private String type;
-    private String uom;
-
-    public String getTime() {
-        return time;
-    }
-
-    public void setTime(String time) {
-        this.time = time;
-    }
-
-    public Double getAltitude() {
-        return altitude;
-    }
-
-    public void setAltitude(Double altitude) {
-        this.altitude = altitude;
-    }
-
-    public Double getLatitude() {
-        return latitude;
-    }
-
-    public void setLatitude(Double latitude) {
-        this.latitude = latitude;
-    }
-
-    public Double getLongitude() {
-        return longitude;
-    }
-
-    public void setLongitude(Double longitude) {
-        this.longitude = longitude;
-    }
-
-    public String getGeohash() {
-        return geohash;
-    }
-
-    public void setGeohash(String geohash) {
-        this.geohash = geohash;
-    }
-
-    public Double getValue() {
-        return value;
-    }
-
-    public void setValue(Double value) {
-        this.value = value;
-    }
-
-    public String getEntityName() {
-        return entityName;
-    }
-
-    public void setEntityName(String entityName) {
-        this.entityName = entityName;
-    }
-
-    public String getSequenceNumber() {
-        return sequenceNumber;
-    }
-
-    public void setSequenceNumber(String sequenceNumber) {
-        this.sequenceNumber = sequenceNumber;
-    }
-
-    public String getService() {
-        return service;
-    }
-
-    public void setService(String service) {
-        this.service = service;
-    }
-
-    public String getProvider() {
-        return provider;
-    }
-
-    public void setProvider(String provider) {
-        this.provider = provider;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public void setType(String type) {
-        this.type = type;
-    }
-
-    public String getUom() {
-        return uom;
-    }
-
-    public void setUom(String uom) {
-        this.uom = uom;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.afarcloud;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Observation {
+
+    private String time;
+    private Double altitude;
+    private Double latitude;
+    private Double longitude;
+    private String geohash;
+    private Double value;
+    private String entityName;
+    private String sequenceNumber;
+    private String service;
+    private String provider;
+    private String type;
+    private String uom;
+
+    public String getTime() {
+        return time;
+    }
+
+    public void setTime(String time) {
+        this.time = time;
+    }
+
+    public Double getAltitude() {
+        return altitude;
+    }
+
+    public void setAltitude(Double altitude) {
+        this.altitude = altitude;
+    }
+
+    public Double getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(Double latitude) {
+        this.latitude = latitude;
+    }
+
+    public Double getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(Double longitude) {
+        this.longitude = longitude;
+    }
+
+    public String getGeohash() {
+        return geohash;
+    }
+
+    public void setGeohash(String geohash) {
+        this.geohash = geohash;
+    }
+
+    public Double getValue() {
+        return value;
+    }
+
+    public void setValue(Double value) {
+        this.value = value;
+    }
+
+    public String getEntityName() {
+        return entityName;
+    }
+
+    public void setEntityName(String entityName) {
+        this.entityName = entityName;
+    }
+
+    public String getSequenceNumber() {
+        return sequenceNumber;
+    }
+
+    public void setSequenceNumber(String sequenceNumber) {
+        this.sequenceNumber = sequenceNumber;
+    }
+
+    public String getService() {
+        return service;
+    }
+
+    public void setService(String service) {
+        this.service = service;
+    }
+
+    public String getProvider() {
+        return provider;
+    }
+
+    public void setProvider(String provider) {
+        this.provider = provider;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getUom() {
+        return uom;
+    }
+
+    public void setUom(String uom) {
+        this.uom = uom;
+    }
+}

+ 34 - 34
connector-model/src/main/java/io/connector/model/afarcloud/ResourceMeasurement.java

@@ -1,34 +1,34 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.afarcloud;
-
-import java.util.List;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class ResourceMeasurement {
-
-    private String resource;
-    private List<SensorTelemetry> measurements;
-
-    public String getResource() {
-        return resource;
-    }
-
-    public void setResource(String resource) {
-        this.resource = resource;
-    }
-
-    public List<SensorTelemetry> getMeasurements() {
-        return measurements;
-    }
-
-    public void setMeasurements(List<SensorTelemetry> measurements) {
-        this.measurements = measurements;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.afarcloud;
+
+import java.util.List;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class ResourceMeasurement {
+
+    private String resource;
+    private List<SensorTelemetry> measurements;
+
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
+    public List<SensorTelemetry> getMeasurements() {
+        return measurements;
+    }
+
+    public void setMeasurements(List<SensorTelemetry> measurements) {
+        this.measurements = measurements;
+    }
+}

+ 64 - 64
connector-model/src/main/java/io/connector/model/afarcloud/ResponseModel.java

@@ -1,64 +1,64 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.afarcloud;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class ResponseModel<T> {
-
-    private String query;
-    private Map<String, String> params;
-    private int numResults;
-    private List<T> results;
-
-    public ResponseModel() {
-        this.params = new HashMap<>();
-        this.results = new ArrayList<>();
-    }
-
-    public String getQuery() {
-        return query;
-    }
-
-    public void setQuery(String query) {
-        this.query = query;
-    }
-
-    public Map<String, String> getParams() {
-        return params;
-    }
-
-    public void setParams(Map<String, String> params) {
-        this.params = params;
-    }
-
-    public void addParam(String name, String value) {
-        this.params.put(name, value);
-    }
-
-    public int getNumResults() {
-        return numResults;
-    }
-
-    public void setNumResults(int numResults) {
-        this.numResults = numResults;
-    }
-
-    public List<T> getResults() {
-        return results;
-    }
-
-    public void setResults(List<T> results) {
-        this.results = results;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.afarcloud;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class ResponseModel<T> {
+
+    private String query;
+    private Map<String, String> params;
+    private int numResults;
+    private List<T> results;
+
+    public ResponseModel() {
+        this.params = new HashMap<>();
+        this.results = new ArrayList<>();
+    }
+
+    public String getQuery() {
+        return query;
+    }
+
+    public void setQuery(String query) {
+        this.query = query;
+    }
+
+    public Map<String, String> getParams() {
+        return params;
+    }
+
+    public void setParams(Map<String, String> params) {
+        this.params = params;
+    }
+
+    public void addParam(String name, String value) {
+        this.params.put(name, value);
+    }
+
+    public int getNumResults() {
+        return numResults;
+    }
+
+    public void setNumResults(int numResults) {
+        this.numResults = numResults;
+    }
+
+    public List<T> getResults() {
+        return results;
+    }
+
+    public void setResults(List<T> results) {
+        this.results = results;
+    }
+}

+ 42 - 42
connector-model/src/main/java/io/connector/model/afarcloud/SensorTelemetry.java

@@ -1,42 +1,42 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.afarcloud;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class SensorTelemetry {
-
-    private String measurement;
-    private List<Observation> observations;
-
-    public String getMeasurement() {
-        return measurement;
-    }
-
-    public void setMeasurement(String measurement) {
-        this.measurement = measurement;
-    }
-
-    public List<Observation> getObservations() {
-        return observations;
-    }
-
-    public void addObservation(Observation observation) {
-        if (this.observations == null) {
-            this.observations = new ArrayList<>();
-        }
-        this.observations.add(observation);
-    }
-
-    public void setObservations(List<Observation> observations) {
-        this.observations = observations;
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.afarcloud;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class SensorTelemetry {
+
+    private String measurement;
+    private List<Observation> observations;
+
+    public String getMeasurement() {
+        return measurement;
+    }
+
+    public void setMeasurement(String measurement) {
+        this.measurement = measurement;
+    }
+
+    public List<Observation> getObservations() {
+        return observations;
+    }
+
+    public void addObservation(Observation observation) {
+        if (this.observations == null) {
+            this.observations = new ArrayList<>();
+        }
+        this.observations.add(observation);
+    }
+
+    public void setObservations(List<Observation> observations) {
+        this.observations = observations;
+    }
+}

+ 69 - 67
connector-model/src/main/java/io/connector/model/afarcloud/SimpleObservation.java

@@ -1,67 +1,69 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.afarcloud;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class SimpleObservation {
-
-    private static class Result {
-        private final Double value;
-        private String uom;
-
-        Result(Double value, String uom) {
-            this.value = value;
-            this.uom = uom;
-        }
-
-        Result(Double value) {
-            this.value = value;
-        }
-
-        public Double getValue() {
-            return value;
-        }
-
-        public String getUom() {
-            return uom;
-        }
-    }
-
-    private String observedProperty;
-    private Long resultTime;
-    private Result result;
-
-    public String getObservedProperty() {
-        return observedProperty;
-    }
-
-    public void setObservedProperty(String observedProperty) {
-        this.observedProperty = observedProperty;
-    }
-
-    public Long getResultTime() {
-        return resultTime;
-    }
-
-    public void setResultTime(Long resultTime) {
-        this.resultTime = resultTime;
-    }
-
-    public Result getResult() {
-        return result;
-    }
-
-    public void setResult(Double value, String uom) {
-        this.result = new Result(value, uom);
-    }
-
-    public void setResult(Double value) {
-        this.result = new Result(value);
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.afarcloud;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class SimpleObservation {
+
+    private static class Result {
+        private final Double value;
+        private String uom;
+
+        Result(Double value, String uom) {
+            this.value = value;
+            this.uom = uom;
+        }
+
+        Result(Double value) {
+            this.value = value;
+        }
+
+        public Double getValue() {
+            return value;
+        }
+
+        public String getUom() {
+            return uom;
+        }
+    }
+
+    private String observedProperty;
+    private Long resultTime;
+    private Result result;
+
+    public String getObservedProperty() {
+        return observedProperty;
+    }
+
+    public void setObservedProperty(String observedProperty) {
+        this.observedProperty = observedProperty;
+    }
+
+    public Long getResultTime() {
+        return resultTime;
+    }
+
+    public void setResultTime(Long resultTime) {
+        this.resultTime = resultTime;
+    }
+
+    public Result getResult() {
+        return result;
+    }
+
+    public void setResult(Double value, String uom) {
+        this.result = new Result(value, uom);
+    }
+
+    public void setResult(Double value) {
+        this.result = new Result(value);
+    }
+
+
+}

+ 0 - 28
connector-model/src/main/java/io/connector/model/sensorthings/Datastream.java

@@ -116,32 +116,4 @@ public class Datastream extends JsonObject {
     public String getResultTime() {
         return getString("resultTime");
     }
-
-
-    public static class UnitOfMeasurement extends JsonObject {
-
-        public void setName(String name) {
-            put("name", name);
-        }
-
-        public String getName() {
-            return getString("name");
-        }
-
-        public void setSymbol(String symbol) {
-            put("symbol", symbol);
-        }
-
-        public String getSymbol() {
-            return getString("symbol");
-        }
-
-        public void setDefinition(String definition) {
-            put("definition", definition);
-        }
-
-        public String getDefinition() {
-            return getString("definition");
-        }
-    }
 }

+ 95 - 0
connector-model/src/main/java/io/connector/model/sensorthings/DatastreamInsert.java

@@ -0,0 +1,95 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+import java.time.OffsetDateTime;
+import java.util.Collections;
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+public class DatastreamInsert {
+
+    private final String name;
+    private final String description;
+    private final UnitOfMeasurement unitOfMeasurement;
+    private final String observationType;
+    private final Geometry observedArea;
+    private final OffsetDateTime[] phenomenonTime;
+    private final List<ObservationInsert> observations;
+    private final ObservedProperty observedProperty;
+    private final SensorInsert sensor;
+
+    public static DatastreamInsert parse(JsonObject json) {
+        requireNonNull(json, "JSON 'Datastream' is empty.");
+        String name = requireNonNull(json.getString("name", null), "Attribute 'Datastream/name' is required.");
+        String description = requireNonNull(json.getString("description", null), "Attribute 'Datastream/description' is required.");
+        UnitOfMeasurement unitOfMeasurement = UnitOfMeasurement.parse(requireNonNull(json.getJsonObject("unitOfMeasurement", null), "Attribute 'Datastream/unitOfMeasurement' is required."));
+        String observationType = requireNonNull(json.getString("observationType", null), "Attribute 'Datastream/observationType' is required.");
+        Geometry observedArea = Geometry.parse(requireNonNull(json.getJsonObject("observedArea", null), "Attribute 'Datastream/observedArea' is required."));
+        String phenomenonTime = requireNonNull(json.getString("phenomenonTime", null), "Attribute 'Datastream/phenomenonTime' is required.");
+        String [] phenomenonTimes = phenomenonTime.split("/");
+        OffsetDateTime firstPhenomenonTime = phenomenonTimes[0].equals("<none>") ? null : OffsetDateTime.parse(phenomenonTimes[0]);
+        OffsetDateTime secondPhenomenonTime = phenomenonTimes[1].equals("<none>") ? null :  OffsetDateTime.parse(phenomenonTimes[1]);
+        requireNonNull(json.getJsonArray("Observations", null), "Attribute 'Datastream/Observations' is required.");
+        ObservedProperty observedProperty = ObservedProperty.parse(requireNonNull(json.getJsonObject("ObservedProperty", null), "Attribute 'Datastream/ObservedProperty' is required."));
+        SensorInsert sensor = SensorInsert.parse(requireNonNull(json.getJsonObject("Sensor", null), "Attribute 'Datastream/Sensor' is required."));
+        return new DatastreamInsert(name, description, unitOfMeasurement, observationType, observedArea,
+                new OffsetDateTime[]{firstPhenomenonTime, secondPhenomenonTime}, Collections.emptyList(), observedProperty, sensor);
+    }
+
+    private DatastreamInsert(String name, String description, UnitOfMeasurement unitOfMeasurement,
+                            String observationType, Geometry observedArea, OffsetDateTime[] phenomenonTime,
+                            List<ObservationInsert> observations, ObservedProperty observedProperty,
+                            SensorInsert sensor)
+    {
+        this.name = name;
+        this.description = description;
+        this.unitOfMeasurement = unitOfMeasurement;
+        this.observationType = observationType;
+        this.observedArea = observedArea;
+        this.phenomenonTime = phenomenonTime;
+        this.observations = observations;
+        this.observedProperty = observedProperty;
+        this.sensor = sensor;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public UnitOfMeasurement getUnitOfMeasurement() {
+        return unitOfMeasurement;
+    }
+
+    public String getObservationType() {
+        return observationType;
+    }
+
+    public Geometry getObservedArea() {
+        return observedArea;
+    }
+
+    public OffsetDateTime[] getPhenomenonTime() {
+        return phenomenonTime;
+    }
+
+    public List<ObservationInsert> getObservations() {
+        return observations;
+    }
+
+    public ObservedProperty getObservedProperty() {
+        return observedProperty;
+    }
+
+    public SensorInsert getSensor() {
+        return sensor;
+    }
+}

+ 90 - 90
connector-model/src/main/java/io/connector/model/sensorthings/FeatureOfInterest.java

@@ -1,90 +1,90 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.sensorthings;
-
-import io.vertx.core.json.JsonObject;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class FeatureOfInterest extends JsonObject {
-
-    public void setId(String id) {
-        put("@iot.id", id);
-    }
-
-    public String getId() {
-        return getString("@iot.id");
-    }
-
-    public void setSelfLink(String selfLink) {
-        put("@iot.selfLink", selfLink);
-    }
-
-    public String getSelfLink() {
-        return getString("@iot.selfLink");
-    }
-
-    public void setObservationsNavigationLink(String observationsNavigationLink) {
-        put("Observations@iot.navigationLink", observationsNavigationLink);
-    }
-
-    public String getObservationsNavigationLink() {
-        return getString("Observations@iot.navigationLink");
-    }
-
-    public void setName(String name) {
-        put("name", name);
-    }
-
-    public String getName() {
-        return getString("name");
-    }
-
-    public void setDescription(String description) {
-        put("description", description);
-    }
-
-    public String getDescription(){
-        return getString("description");
-    }
-
-    public void setEncodingType(String encodingType) {
-        put("encodingType", encodingType);
-    }
-
-    public String getEncodingType() {
-        return getString("encodingType");
-    }
-
-    public void setFeature(Feature feature) {
-        put("feature", feature);
-    }
-
-    public Feature getFeature() {
-        return (Feature)getJsonObject("feature");
-    }
-
-    public static class Feature extends JsonObject {
-
-        public void setType(String type) {
-            put("type", type);
-        }
-
-        public String getType() {
-            return getString("type");
-        }
-
-        public void setGeometry(Geometry geometry) {
-            put("geometry", geometry);
-        }
-
-        public Geometry getGeometry() {
-            return (Geometry)getJsonObject("geometry");
-        }
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class FeatureOfInterest extends JsonObject {
+
+    public void setId(String id) {
+        put("@iot.id", id);
+    }
+
+    public String getId() {
+        return getString("@iot.id");
+    }
+
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
+    }
+
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
+    }
+
+    public void setObservationsNavigationLink(String observationsNavigationLink) {
+        put("Observations@iot.navigationLink", observationsNavigationLink);
+    }
+
+    public String getObservationsNavigationLink() {
+        return getString("Observations@iot.navigationLink");
+    }
+
+    public void setName(String name) {
+        put("name", name);
+    }
+
+    public String getName() {
+        return getString("name");
+    }
+
+    public void setDescription(String description) {
+        put("description", description);
+    }
+
+    public String getDescription(){
+        return getString("description");
+    }
+
+    public void setEncodingType(String encodingType) {
+        put("encodingType", encodingType);
+    }
+
+    public String getEncodingType() {
+        return getString("encodingType");
+    }
+
+    public void setFeature(Feature feature) {
+        put("feature", feature);
+    }
+
+    public Feature getFeature() {
+        return (Feature)getJsonObject("feature");
+    }
+
+    public static class Feature extends JsonObject {
+
+        public void setType(String type) {
+            put("type", type);
+        }
+
+        public String getType() {
+            return getString("type");
+        }
+
+        public void setGeometry(Geometry geometry) {
+            put("geometry", geometry);
+        }
+
+        public Geometry getGeometry() {
+            return (Geometry)getJsonObject("geometry");
+        }
+    }
+}

+ 1 - 1
connector-model/src/main/java/io/connector/model/sensorthings/Filter.java

@@ -74,7 +74,7 @@ public class Filter {
 
     private List<Expression> expressionsAdd;
 
-    public static Filter emptyFilter() {
+    private static Filter emptyFilter() {
         return new Filter(false, "");
     }
 

+ 15 - 1
connector-model/src/main/java/io/connector/model/sensorthings/Geometry.java

@@ -6,7 +6,9 @@ package io.connector.model.sensorthings;
 import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  *
@@ -16,6 +18,11 @@ import java.util.List;
  */
 public class Geometry extends JsonObject {
 
+    public static Geometry parse(JsonObject json) {
+        Objects.requireNonNull(json, "JSON 'Geometry' is empty.");
+        return json.mapTo(Geometry.class);
+    }
+
     public void setType(String type) {
         put("type", type);
     }
@@ -29,6 +36,13 @@ public class Geometry extends JsonObject {
     }
 
     public List<Double> getCoordinates() {
-        return (List<Double>) getJsonArray("coordinates").getList();
+        List<?> jsonArr = getJsonArray("coordinates").getList();
+        List<Double> result = new ArrayList<>(jsonArr.size());
+        for (Object val : jsonArr) {
+            if (val instanceof Double) {
+                result.add((Double)val);
+            }
+        }
+        return result;
     }
 }

+ 55 - 55
connector-model/src/main/java/io/connector/model/sensorthings/HistoricalLocation.java

@@ -1,55 +1,55 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.sensorthings;
-
-import io.vertx.core.json.JsonObject;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class HistoricalLocation extends JsonObject {
-
-    public void setId(String id) {
-        put("@iot.id", id);
-    }
-
-    public String getId() {
-        return getString("@iot.id");
-    }
-
-    public void setSelfLink(String selfLink) {
-        put("@iot.selfLink", selfLink);
-    }
-
-    public String getSelfLink() {
-        return getString("@iot.selfLink");
-    }
-
-    public void setLocationsNavigationLink(String locationNavigationLink) {
-        put("Locations@iot.navigationLink", locationNavigationLink);
-    }
-
-    public String getLocationsNavigationLink() {
-        return getString("Locations@iot.navigationLink");
-    }
-
-    public void setThingNavigationLink(String thingsNavigationLink) {
-        put("Thing@iot.navigationLink", thingsNavigationLink);
-    }
-
-    public String getThingNavigationLink() {
-        return getString("Thing@iot.navigationLink");
-    }
-
-    public void setTime(String time) {
-        put("time", time);
-    }
-
-    public String getTime() {
-        return getString("time");
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class HistoricalLocation extends JsonObject {
+
+    public void setId(String id) {
+        put("@iot.id", id);
+    }
+
+    public String getId() {
+        return getString("@iot.id");
+    }
+
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
+    }
+
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
+    }
+
+    public void setLocationsNavigationLink(String locationNavigationLink) {
+        put("Locations@iot.navigationLink", locationNavigationLink);
+    }
+
+    public String getLocationsNavigationLink() {
+        return getString("Locations@iot.navigationLink");
+    }
+
+    public void setThingNavigationLink(String thingsNavigationLink) {
+        put("Thing@iot.navigationLink", thingsNavigationLink);
+    }
+
+    public String getThingNavigationLink() {
+        return getString("Thing@iot.navigationLink");
+    }
+
+    public void setTime(String time) {
+        put("time", time);
+    }
+
+    public String getTime() {
+        return getString("time");
+    }
+}

+ 43 - 25
connector-model/src/main/java/io/connector/model/sensorthings/Location.java

@@ -5,6 +5,8 @@ package io.connector.model.sensorthings;
 
 import io.vertx.core.json.JsonObject;
 
+import java.util.Objects;
+
 /**
  *
  * @author Lukas Cerny
@@ -13,6 +15,41 @@ import io.vertx.core.json.JsonObject;
  */
 public class Location extends JsonObject {
 
+    public static Location parse(JsonObject json) {
+        Objects.requireNonNull(json, "JSON 'Location' is empty.");
+        Location location = json.mapTo(Location.class);
+        location.valid();
+        return location;
+    }
+
+    private void valid() {
+        try {
+            Objects.requireNonNull(getName(), "Attribute 'Location/name' is required.");
+            Objects.requireNonNull(getDescription(), "Attribute 'Location/description' is required.");
+            Objects.requireNonNull(getEncodingType(), "Attribute 'Location/encodingType' is required.");
+            Objects.requireNonNull(getLocation(), "Attribute 'Location/location' is required.");
+        } catch (NullPointerException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    public boolean isValid() {
+        try {
+            valid(); return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+    public boolean isValidWithLink() {
+        boolean preValid = isValid();
+        if (!preValid) { return false; }
+
+        // TODO
+
+        return true;
+    }
+
     public void setId(String id) {
         put("@iot.id", id);
     }
@@ -50,7 +87,7 @@ public class Location extends JsonObject {
     }
 
     public String getEncodingType() {
-        return getString("encodingType");
+        return getString("encodingType", null);
     }
 
     public void setName(String name) {
@@ -58,7 +95,7 @@ public class Location extends JsonObject {
     }
 
     public String getName() {
-        return getString("name");
+        return getString("name", null);
     }
 
     public void setDescription(String description) {
@@ -66,33 +103,14 @@ public class Location extends JsonObject {
     }
 
     public String getDescription(){
-        return getString("description");
+        return getString("description", null);
     }
 
-    public void setLocation(Info location) {
+    public void setLocation(LocationInfo location) {
         put("location", location);
     }
 
-    public Info getLocation() {
-        return (Info) getJsonObject("location");
-    }
-
-    public static class Info extends JsonObject {
-
-        public void setType(String type) {
-            put("type", type);
-        }
-
-        public String getType() {
-            return getString("type");
-        }
-
-        public void setGeometry(Geometry geometry) {
-            put("geometry", geometry);
-        }
-
-        public Geometry getGeometry() {
-            return (Geometry)getJsonObject("geometry");
-        }
+    public LocationInfo getLocation() {
+        return (LocationInfo) getJsonObject("location", null);
     }
 }

+ 29 - 0
connector-model/src/main/java/io/connector/model/sensorthings/LocationInfo.java

@@ -0,0 +1,29 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+public class LocationInfo extends JsonObject {
+
+    public static LocationInfo parse(JsonObject json) {
+        return json != null ? json.mapTo(LocationInfo.class) : null;
+    }
+
+    public void setType(String type) {
+        put("type", type);
+    }
+
+    public String getType() {
+        return getString("type");
+    }
+
+    public void setGeometry(Geometry geometry) {
+        put("geometry", geometry);
+    }
+
+    public Geometry getGeometry() {
+        return (Geometry)getJsonObject("geometry");
+    }
+}

+ 71 - 71
connector-model/src/main/java/io/connector/model/sensorthings/Observation.java

@@ -1,71 +1,71 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.sensorthings;
-
-import io.vertx.core.json.JsonObject;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Observation extends JsonObject {
-
-    public void setId(String id) {
-        put("@iot.id", id);
-    }
-
-    public String getId() {
-        return getString("@iot.id");
-    }
-
-    public void setSelfLink(String selfLink) {
-        put("@iot.selfLink", selfLink);
-    }
-
-    public String getSelfLink() {
-        return getString("@iot.selfLink");
-    }
-
-    public void setFeatureOfInterestNavigationLink(String featureOfInterestNavigationLink) {
-        put("FeatureOfInterest@iot.navigationLink", featureOfInterestNavigationLink);
-    }
-
-    public String getFeatureOfInterestNavigationLink() {
-        return getString("FeatureOfInterest@iot.navigationLink");
-    }
-
-    public void setDataStreamNavigationLink(String dataStreamNavigationLink) {
-        put("Datastreams@iot.navigationLink", dataStreamNavigationLink);
-    }
-
-    public String getDataStreamNavigationLink() {
-        return getString("Datastreams@iot.navigationLink");
-    }
-
-    public void setPhenomenonTime(String phenomenonTime) {
-        put("phenomenonTime", phenomenonTime);
-    }
-
-    public String getPhenomenonTime() {
-        return getString("phenomenonTime");
-    }
-
-    public void setResultTime(String resultTime) {
-        put("resultTime", resultTime);
-    }
-
-    public String getResultTime() {
-        return getString("resultTime");
-    }
-
-    public void setResult(Double result) {
-        put("result", result);
-    }
-
-    public Double getResult() {
-        return getDouble("result");
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Observation extends JsonObject {
+
+    public void setId(String id) {
+        put("@iot.id", id);
+    }
+
+    public String getId() {
+        return getString("@iot.id");
+    }
+
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
+    }
+
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
+    }
+
+    public void setFeatureOfInterestNavigationLink(String featureOfInterestNavigationLink) {
+        put("FeatureOfInterest@iot.navigationLink", featureOfInterestNavigationLink);
+    }
+
+    public String getFeatureOfInterestNavigationLink() {
+        return getString("FeatureOfInterest@iot.navigationLink");
+    }
+
+    public void setDataStreamNavigationLink(String dataStreamNavigationLink) {
+        put("Datastreams@iot.navigationLink", dataStreamNavigationLink);
+    }
+
+    public String getDataStreamNavigationLink() {
+        return getString("Datastreams@iot.navigationLink");
+    }
+
+    public void setPhenomenonTime(String phenomenonTime) {
+        put("phenomenonTime", phenomenonTime);
+    }
+
+    public String getPhenomenonTime() {
+        return getString("phenomenonTime");
+    }
+
+    public void setResultTime(String resultTime) {
+        put("resultTime", resultTime);
+    }
+
+    public String getResultTime() {
+        return getString("resultTime");
+    }
+
+    public void setResult(Double result) {
+        put("result", result);
+    }
+
+    public Double getResult() {
+        return getDouble("result");
+    }
+}

+ 53 - 0
connector-model/src/main/java/io/connector/model/sensorthings/ObservationInsert.java

@@ -0,0 +1,53 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+import java.time.OffsetDateTime;
+import java.util.Objects;
+
+public class ObservationInsert {
+
+    private final String dataStreamId;
+    private final OffsetDateTime phenomenonTime;
+    private final Double result;
+    private final String featureOfInterestId;
+
+    private ObservationInsert(String dataStreamId, OffsetDateTime phenomenonTime, Double result, String featureOfInterestId) {
+        Objects.requireNonNull(dataStreamId, "Attribute 'Datastream' and specified '@iot.id' is required.");
+        Objects.requireNonNull(phenomenonTime, "Attribute 'phenomenonTime' is required.");
+        Objects.requireNonNull(result, "Attribute 'result' is required.");
+
+        this.dataStreamId = dataStreamId;
+        this.phenomenonTime = phenomenonTime;
+        this.result = result;
+        this.featureOfInterestId = featureOfInterestId;
+    }
+
+    public static ObservationInsert parse(JsonObject jsonObject) {
+        return new ObservationInsert(
+                jsonObject.getJsonObject("Datastream", new JsonObject()).getString("@iot.id", null),
+                OffsetDateTime.parse(jsonObject.getString("phenomenonTime", null)),
+                jsonObject.getDouble("result", null),
+                jsonObject.getJsonObject("FeatureOfInterest", new JsonObject()).getString("@iot.id", null)
+        );
+    }
+
+    public String getDatastreamId() {
+        return dataStreamId;
+    }
+
+    public OffsetDateTime getPhenomenonTime() {
+        return phenomenonTime;
+    }
+
+    public Double getResult() {
+        return result;
+    }
+
+    public String getFeatureOfInterestId() {
+        return featureOfInterestId;
+    }
+}

+ 7 - 0
connector-model/src/main/java/io/connector/model/sensorthings/ObservedProperty.java

@@ -5,6 +5,8 @@ package io.connector.model.sensorthings;
 
 import io.vertx.core.json.JsonObject;
 
+import java.util.Objects;
+
 /**
  *
  * @author Lukas Cerny
@@ -13,6 +15,11 @@ import io.vertx.core.json.JsonObject;
  */
 public class ObservedProperty extends JsonObject {
 
+    public static ObservedProperty parse(JsonObject json) {
+        Objects.requireNonNull(json,"JSON 'ObservedProperty' is empty.");
+        return json.mapTo(ObservedProperty.class);
+    }
+
     public void setId(String id) {
         put("@iot.id", id);
     }

+ 9 - 0
connector-model/src/main/java/io/connector/model/sensorthings/Params.java

@@ -0,0 +1,9 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+public final class Params {
+
+    public static final String FILTER = "$filter";
+}

+ 71 - 71
connector-model/src/main/java/io/connector/model/sensorthings/Sensor.java

@@ -1,71 +1,71 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.sensorthings;
-
-import io.vertx.core.json.JsonObject;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Sensor extends JsonObject {
-
-    public void setId(String id) {
-        put("@iot.id", id);
-    }
-
-    public String getId() {
-        return getString("@iot.id");
-    }
-
-    public void setSelfLink(String selfLink) {
-        put("@iot.selfLink", selfLink);
-    }
-
-    public String getSelfLink() {
-        return getString("@iot.selfLink");
-    }
-
-    public void setDataStreamNavigationLink(String dataStreamNavigationLink) {
-        put("Datastreams@iot.navigationLink", dataStreamNavigationLink);
-    }
-
-    public String getDataStreamNavigationLink() {
-        return getString("Datastreams@iot.navigationLink");
-    }
-
-    public void setName(String name) {
-        put("name", name);
-    }
-
-    public String getName() {
-        return getString("name");
-    }
-
-    public void setDescription(String description) {
-        put("description", description);
-    }
-
-    public String getDescription(){
-        return getString("description");
-    }
-
-    public void setEncodingType(String encodingType) {
-        put("encodingType", encodingType);
-    }
-
-    public String getEncodingType() {
-        return getString("encodingType");
-    }
-
-    public void setMetadata(String metadata) {
-        put("metadata", metadata);
-    }
-
-    public String getMetadata() {
-        return getString("metadata");
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Sensor extends JsonObject {
+
+    public void setId(String id) {
+        put("@iot.id", id);
+    }
+
+    public String getId() {
+        return getString("@iot.id");
+    }
+
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
+    }
+
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
+    }
+
+    public void setDataStreamNavigationLink(String dataStreamNavigationLink) {
+        put("Datastreams@iot.navigationLink", dataStreamNavigationLink);
+    }
+
+    public String getDataStreamNavigationLink() {
+        return getString("Datastreams@iot.navigationLink");
+    }
+
+    public void setName(String name) {
+        put("name", name);
+    }
+
+    public String getName() {
+        return getString("name");
+    }
+
+    public void setDescription(String description) {
+        put("description", description);
+    }
+
+    public String getDescription(){
+        return getString("description");
+    }
+
+    public void setEncodingType(String encodingType) {
+        put("encodingType", encodingType);
+    }
+
+    public String getEncodingType() {
+        return getString("encodingType");
+    }
+
+    public void setMetadata(String metadata) {
+        put("metadata", metadata);
+    }
+
+    public String getMetadata() {
+        return getString("metadata");
+    }
+}

+ 48 - 0
connector-model/src/main/java/io/connector/model/sensorthings/SensorInsert.java

@@ -0,0 +1,48 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+import static java.util.Objects.requireNonNull;
+
+public class SensorInsert {
+
+    private final String name;
+    private final String description;
+    private final String encodingType;
+    private final String metadata;
+
+    public static SensorInsert parse(JsonObject json) {
+        requireNonNull(json, "JSON 'Sensor' is empty.");
+        String name = requireNonNull(json.getString("name", null), "Attribute 'Sensor/name' is required.");
+        String description = requireNonNull(json.getString("description", null), "Attribute 'Sensor/description' is required.");
+        String encodingType = requireNonNull(json.getString("encodingType", null), "Attribute 'Sensor/encodingType' is required.");
+        String metadata = requireNonNull(json.getString("metadata", null), "Attribute 'Sensor/metadata' is required.");
+        return new SensorInsert(name, description, encodingType, metadata);
+    }
+
+    private SensorInsert(String name, String description, String encodingType, String metadata) {
+        this.name = name;
+        this.description = description;
+        this.encodingType = encodingType;
+        this.metadata = metadata;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getEncodingType() {
+        return encodingType;
+    }
+
+    public String getMetadata() {
+        return metadata;
+    }
+}

+ 80 - 80
connector-model/src/main/java/io/connector/model/sensorthings/Thing.java

@@ -1,80 +1,80 @@
-// Copyright (c) 2020 UWB & LESP.
-// The UWB & LESP license this file to you under the MIT license.
-
-package io.connector.model.sensorthings;
-
-import io.vertx.core.json.JsonObject;
-
-/**
- *
- * @author Lukas Cerny
- * @version 1.0
- * @since 1.0
- */
-public class Thing extends JsonObject {
-
-    public void setId(String id) {
-        put("@iot.id", id);
-    }
-
-    public String getId() {
-        return getString("@iot.id");
-    }
-
-    public void setSelfLink(String selfLink) {
-        put("@iot.selfLink", selfLink);
-    }
-
-    public String getSelfLink() {
-        return getString("@iot.selfLink");
-    }
-
-    public void setLocationsNavigationLink(String locationNavigationLink) {
-        put("Locations@iot.navigationLink", locationNavigationLink);
-    }
-
-    public String getLocationsNavigationLink() {
-        return getString("Locations@iot.navigationLink");
-    }
-
-    public void setDataStreamNavigationLink(String dataStreamNavigationLink) {
-        put("Datastreams@iot.navigationLink", dataStreamNavigationLink);
-    }
-
-    public String getDataStreamNavigationLink() {
-        return getString("Datastreams@iot.navigationLink");
-    }
-
-    public void setHistoricalLocationsNavigationLink(String historicalLocationsNavigationLink) {
-        put("HistoricalLocations@iot.navigationLink", historicalLocationsNavigationLink);
-    }
-
-    public String getHistoricalLocationsNavigationLink() {
-        return getString("HistoricalLocations@iot.navigationLink");
-    }
-
-
-    public void setName(String name) {
-        put("name", name);
-    }
-
-    public String getName() {
-        return getString("name");
-    }
-
-    public void setDescription(String description) {
-        put("description", description);
-    }
-
-    public String getDescription(){
-        return getString("description");
-    }
-
-    public void setProperties(JsonObject properties) {
-        put("properties", properties);
-    }
-
-    public JsonObject getProperties() {
-        return getJsonObject("properties");
-    }
-}
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+/**
+ *
+ * @author Lukas Cerny
+ * @version 1.0
+ * @since 1.0
+ */
+public class Thing extends JsonObject {
+
+    public void setId(String id) {
+        put("@iot.id", id);
+    }
+
+    public String getId() {
+        return getString("@iot.id");
+    }
+
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
+    }
+
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
+    }
+
+    public void setLocationsNavigationLink(String locationNavigationLink) {
+        put("Locations@iot.navigationLink", locationNavigationLink);
+    }
+
+    public String getLocationsNavigationLink() {
+        return getString("Locations@iot.navigationLink");
+    }
+
+    public void setDataStreamNavigationLink(String dataStreamNavigationLink) {
+        put("Datastreams@iot.navigationLink", dataStreamNavigationLink);
+    }
+
+    public String getDataStreamNavigationLink() {
+        return getString("Datastreams@iot.navigationLink");
+    }
+
+    public void setHistoricalLocationsNavigationLink(String historicalLocationsNavigationLink) {
+        put("HistoricalLocations@iot.navigationLink", historicalLocationsNavigationLink);
+    }
+
+    public String getHistoricalLocationsNavigationLink() {
+        return getString("HistoricalLocations@iot.navigationLink");
+    }
+
+
+    public void setName(String name) {
+        put("name", name);
+    }
+
+    public String getName() {
+        return getString("name");
+    }
+
+    public void setDescription(String description) {
+        put("description", description);
+    }
+
+    public String getDescription(){
+        return getString("description");
+    }
+
+    public void setProperties(JsonObject properties) {
+        put("properties", properties);
+    }
+
+    public JsonObject getProperties() {
+        return getJsonObject("properties");
+    }
+}

+ 70 - 0
connector-model/src/main/java/io/connector/model/sensorthings/ThingInsert.java

@@ -0,0 +1,70 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+import java.util.*;
+
+import static java.util.Objects.requireNonNull;
+
+public class ThingInsert {
+
+    private final String name;
+    private final String description;
+    private final List<Location> locations;
+    private final List<DatastreamInsert> datastreams;
+    private final Map<String, Object> properties;
+
+    public static ThingInsert parse(JsonObject json) {
+        Objects.requireNonNull(json, "JSON 'Thing' is empty.");
+        String name = requireNonNull(json.getString("name", null), "Attribute 'name' is required.");
+        String description = requireNonNull(json.getString("description", null), "Attribute 'description' is required.");
+        JsonArray locationsJson = requireNonNull(json.getJsonArray("Locations", null), "Attribute 'Location' is required.");
+        List<Location> locations = new ArrayList<>(locationsJson.size());
+        for (int i = 0; i < locationsJson.size(); i++) {
+            locations.add(Location.parse(locationsJson.getJsonObject(i)));
+        }
+        JsonArray datastreamsJson = requireNonNull(json.getJsonArray("Datastreams", null), "Attribute 'Datastreams' is required.");
+        List<DatastreamInsert> datastreams = new ArrayList<>(datastreamsJson.size());
+        for (int i = 0; i < datastreamsJson.size(); i++) {
+            datastreams.add(DatastreamInsert.parse(datastreamsJson.getJsonObject(i)));
+        }
+        Map<String, Object> properties = json.getJsonObject("properties", new JsonObject()).getMap();
+        return new ThingInsert(name, description, locations, datastreams, properties);
+    }
+
+    public ThingInsert(String name, String description, List<Location> locations, List<DatastreamInsert> datastreams, Map<String, Object> properties) {
+        this.name = name;
+        this.description = description;
+        this.locations = locations;
+        this.datastreams = datastreams;
+        this.properties = properties.isEmpty() ? null : properties;
+    }
+
+    public ThingInsert(String name, String description, List<Location> locations, List<DatastreamInsert> datastreams) {
+        this(name, description, locations, datastreams, Collections.emptyMap());
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public List<Location> getLocations() {
+        return locations;
+    }
+
+    public List<DatastreamInsert> getDatastreams() {
+        return datastreams;
+    }
+
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+}

+ 40 - 0
connector-model/src/main/java/io/connector/model/sensorthings/UnitOfMeasurement.java

@@ -0,0 +1,40 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+import java.util.Objects;
+
+public class UnitOfMeasurement extends JsonObject {
+
+    public static UnitOfMeasurement parse(JsonObject json) {
+        Objects.requireNonNull(json, "JSON 'UnitOfMeasurement' is empty.");
+        return json.mapTo(UnitOfMeasurement.class);
+    }
+
+    public void setName(String name) {
+        put("name", name);
+    }
+
+    public String getName() {
+        return getString("name");
+    }
+
+    public void setSymbol(String symbol) {
+        put("symbol", symbol);
+    }
+
+    public String getSymbol() {
+        return getString("symbol");
+    }
+
+    public void setDefinition(String definition) {
+        put("definition", definition);
+    }
+
+    public String getDefinition() {
+        return getString("definition");
+    }
+}

+ 71 - 5
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCHttpClient.java

@@ -8,16 +8,21 @@ import io.connector.core.http.HttpRequest;
 import io.connector.core.http.HttpResponse;
 import io.connector.core.http.URLBuilder;
 import io.connector.core.config.HostConfig;
+import io.connector.core.util.Tuple;
 import io.connector.model.afarcloud.MultiSensor;
+import io.connector.model.afarcloud.MultiSimpleObservation;
 import io.connector.model.afarcloud.ResourceMeasurement;
+import io.connector.model.afarcloud.SimpleObservation;
 import io.vertx.core.json.Json;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Function;
 
-import static io.connector.core.http.ContentType.TEXT_PLAIN;
+import static io.connector.core.http.HttpContentType.TEXT_PLAIN;
 import static java.lang.String.format;
 
 /**
@@ -60,6 +65,60 @@ public class AFCHttpClient {
         this.httpClient = httpClient;
     }
 
+    public void uploadAggregatedMeasurements(List<MultiSimpleObservation> observations) {
+
+        if (observations == null || observations.isEmpty()) {
+            logger.warn("Nothing to upload. Received observations of multi-sensor are empty."); return;
+        }
+
+        HostConfig host = new HostConfig(config.getTelemetryDomain(), "telemetry");
+
+        List<Tuple<MultiSimpleObservation, String>> failedObservations = new ArrayList<>();
+
+        for (MultiSimpleObservation multiSensor : observations) {
+
+            HttpRequest request = HttpRequest.newBuilder().POST()
+                    .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
+                            //    .addParam("test", "")
+                            .build())
+                    .body(Json.encode(multiSensor))
+                    .build();
+            logger.info("Creating a http request to {}.", request);
+
+            HttpResponse response = httpClient.send(request);
+            logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());
+
+            if (response.isError()) {
+                failedObservations.add(Tuple.of(multiSensor, response.getBody()));
+            }
+        }
+
+        logger.info("Uploaded {}/{} observations.", observations.size()-failedObservations.size(), observations.size());
+
+        if (!failedObservations.isEmpty()) {
+            final Function<Tuple<MultiSimpleObservation, String>, String> formatError = t -> {
+                List<SimpleObservation> sObs = t.getItem1().getObservations();
+                StringBuilder propertiesBuilder = new StringBuilder("[");
+                for (int obI = 0; obI < sObs.size() - 1; obI++) {
+                    propertiesBuilder.append(sObs.get(obI).getObservedProperty()).append(",");
+                }
+                propertiesBuilder.append(sObs.get(sObs.size() - 1).getObservedProperty()).append("]");
+                return "resourceId: " + t.getItem1().getResourceId() +
+                        " with observedProperty: " + propertiesBuilder +
+                        " appeared an error: " + t.getItem2();
+            };
+
+            StringBuilder resourceIdBuilder = new StringBuilder("[");
+            for (int i = 0; i < failedObservations.size() - 1; i++) {
+                resourceIdBuilder.append(formatError.apply(failedObservations.get(i))).append(", ");
+            }
+            resourceIdBuilder.append(formatError.apply(failedObservations.get(failedObservations.size()-1))).append("]");
+            throw logger.throwing(new RuntimeException(String.format(
+                    "Observations for following resourceIds can not be send: %s.", resourceIdBuilder
+            )));
+        }
+    }
+
     public List<ResourceMeasurement> getObservationsBySensor(Filter filter) {
         HostConfig host = new HostConfig(config.getRetrievalDomain(), "getObservationsBySensor/historic");
         logger.debug("Getting observations from {}.", host.getDomain());
@@ -92,14 +151,21 @@ public class AFCHttpClient {
         logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());
 
         if (response.isError()) {
-            logger.error("Can not get data from the server {}. Error {} {}",
-                    host.getDomain(), response.getStatus(), response.getBody());
-            throw new RuntimeException(response.getBody());
+            throw logger.throwing(new RuntimeException(String.format(
+                    "Can not get data from the server %s. Error %d %s",
+                    host.getDomain(), response.getStatus(), response.getBody())
+            ));
         }
 
         logger.debug("Parsing body of the response to the class {}.", ResponseOfMeasurement.class);
         ResponseOfMeasurement responseModel = Json.decodeValue(response.getBody(), ResponseOfMeasurement.class);
-        if (responseModel == null || responseModel.results == null || responseModel.results.resources == null) {
+        if (responseModel == null) {
+            throw logger.throwing(new RuntimeException(String.format(
+                    "Received JSON data can not be parsed. Data: %s", response.getBody()
+            )));
+        }
+
+        if (responseModel.results == null || responseModel.results.resources == null) {
             return Collections.emptyList();
         }
         return responseModel.results.resources;

+ 254 - 32
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java

@@ -5,13 +5,16 @@ package io.connector.module.afarcloud.gateway;
 
 import io.connector.core.AbstractGateway;
 import io.connector.core.http.RequestUriComponent;
-import io.connector.model.afarcloud.MultiSensor;
-import io.connector.model.afarcloud.ResourceMeasurement;
-import io.connector.model.afarcloud.SensorTelemetry;
+import io.connector.model.afarcloud.*;
 import io.connector.model.sensorthings.*;
+import io.connector.model.sensorthings.Location;
+import io.connector.model.sensorthings.Observation;
 import io.connector.module.afarcloud.AFCHttpClient;
 import io.connector.module.afarcloud.Filter;
+import io.vertx.core.json.Json;
+import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.handler.BodyHandler;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -19,14 +22,15 @@ import java.time.Instant;
 import java.util.*;
 import java.util.function.Supplier;
 
-import static io.connector.core.http.ContentType.APPLICATION_JSON;
-import static io.connector.core.http.Header.CONTENT_TYPE;
+import static io.connector.core.http.HttpContentType.APPLICATION_JSON;
+import static io.connector.core.http.HttpHeader.CONTENT_TYPE;
 import static io.connector.core.AddressPath.Creator.create;
 import static io.vertx.core.json.Json.encode;
 import static java.lang.Math.sqrt;
 import static java.lang.StrictMath.pow;
 import static java.lang.String.format;
 import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
 import static java.util.Optional.ofNullable;
 
 /**
@@ -55,6 +59,18 @@ public class OGCSensorThingsGateway extends AbstractGateway {
     @Override
     protected void run() {
 
+        router().post().handler(BodyHandler.create()).handler(ctx -> {
+            String contentType = ctx.request().getHeader(CONTENT_TYPE);
+            if (!contentType.equals(APPLICATION_JSON)) {
+                ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).setStatusCode(415)
+                        .end(new JsonObject().put("message", String.format(
+                                        "Unsupported content type. Use one of these [%s].", APPLICATION_JSON
+                                )).encode()
+                        );
+            }
+            ctx.next();
+        });
+
         router().get(create("Things")).handler(ctx -> {
             logger.info("Handling a request: {}.", ctx.request().absoluteURI());
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
@@ -230,7 +246,7 @@ public class OGCSensorThingsGateway extends AbstractGateway {
             ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON);
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
             String id = ctx.pathParam("id"); // resourceUrn + observedProperty
-            String filterParam = ctx.request().getParam("filter");
+            String filterParam = ctx.request().getParam(Params.FILTER);
 
             io.connector.model.sensorthings.Filter filter = null;
             try {
@@ -262,16 +278,20 @@ public class OGCSensorThingsGateway extends AbstractGateway {
                 );
             }
 
-            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Datastream with @iot.id \"" + id + "\".");
+            if (measurements.isEmpty()) {
+                ctx.response().end(new JsonArray().encode());
+            } else {
+                final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find Datastream with @iot.id \"" + id + "\".");
 
-            Optional<ResourceMeasurement> measurementOpt = ofNullable(measurements.size() == 1 ? measurements.get(0) : null);
-            ResourceMeasurement afcMeasurement = measurementOpt.orElseThrow(exception);
+                Optional<ResourceMeasurement> measurementOpt = ofNullable(measurements.size() == 1 ? measurements.get(0) : null);
+                ResourceMeasurement afcMeasurement = measurementOpt.orElseThrow(exception);
 
-            Optional<SensorTelemetry> telemetryOpt = ofNullable(afcMeasurement.getMeasurements().size() == 1 ? afcMeasurement.getMeasurements().get(0) : null);
-            SensorTelemetry afcTelemetry = telemetryOpt.orElseThrow(exception);
+                Optional<SensorTelemetry> telemetryOpt = ofNullable(afcMeasurement.getMeasurements().size() == 1 ? afcMeasurement.getMeasurements().get(0) : null);
+                SensorTelemetry afcTelemetry = telemetryOpt.orElseThrow(exception);
 
-            List<Observation> ogcObservations = Converter.convertToObservations(afcMeasurement, afcTelemetry, uriComponent);
-            ctx.response().end(encode(ogcObservations));
+                List<Observation> ogcObservations = Converter.convertToObservations(afcMeasurement, afcTelemetry, uriComponent);
+                ctx.response().end(encode(ogcObservations));
+            }
         });
 
         router().get(create("FeaturesOfInterest(:id)")).handler(ctx -> {
@@ -331,7 +351,7 @@ public class OGCSensorThingsGateway extends AbstractGateway {
             AFCAggrLocation afcLastLocation = afcLastLocationOpt.orElseThrow(exception);
 
             Location ogcLocation = Converter.convertToLocation(afcMultiSensor, afcLastLocation, uriComponent);
-            List<Location> ogcLocations = Collections.singletonList(ogcLocation);
+            List<Location> ogcLocations = singletonList(ogcLocation);
             ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(encode(ogcLocations));
         });
 
@@ -376,7 +396,7 @@ public class OGCSensorThingsGateway extends AbstractGateway {
             ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON);
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
             String resourceUrn = ctx.pathParam("id"); // resourceUrn
-            String filterParam = ctx.request().getParam("filter");
+            String filterParam = ctx.request().getParam(Params.FILTER);
 
             io.connector.model.sensorthings.Filter filter = null;
             try {
@@ -404,15 +424,19 @@ public class OGCSensorThingsGateway extends AbstractGateway {
                 );
             }
 
-            final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find HistoricalLocations of the Thing with @iot.id \"" + resourceUrn + "\".");
+            if (afcMeasurements.isEmpty()) {
+                ctx.response().end(new JsonArray().encode());
+            } else {
+                final Supplier<IllegalArgumentException> exception = () -> new IllegalArgumentException("Can not find HistoricalLocations of the Thing with @iot.id \"" + resourceUrn + "\".");
 
-            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
-            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+                Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+                ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
 
-            AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
+                AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
 
-            List<HistoricalLocation> locations = Converter.convertToHistoricalLocation(afcMultiSensor, afcLocations.getList(), uriComponent);
-            ctx.response().end(encode(locations));
+                List<HistoricalLocation> locations = Converter.convertToHistoricalLocation(afcMultiSensor, afcLocations.getList(), uriComponent);
+                ctx.response().end(encode(locations));
+            }
         });
 
         router().get(create("Locations(:id)/HistoricalLocations")).handler(ctx -> {
@@ -420,7 +444,7 @@ public class OGCSensorThingsGateway extends AbstractGateway {
             ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON);
             RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
             String id = ctx.pathParam("id"); // resourceUrn + observedProperty + time
-            String filterParam = ctx.request().getParam("filter");
+            String filterParam = ctx.request().getParam(Params.FILTER);
 
             io.connector.model.sensorthings.Filter filter = null;
             try {
@@ -458,14 +482,48 @@ public class OGCSensorThingsGateway extends AbstractGateway {
                     .entityNames(afcMultiSensor.getResourceId()).measurements(observedProperty)
             );
 
-            Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
-            ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+            if (afcMeasurements.isEmpty()) {
+                ctx.response().end(new JsonArray().encode());
+            } else {
 
-            AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
+                Optional<ResourceMeasurement> afcMeasurementOpt = ofNullable(afcMeasurements.size() == 1 ? afcMeasurements.get(0) : null);
+                ResourceMeasurement afcMeasurement = afcMeasurementOpt.orElseThrow(exception);
+
+                AFCLocationList afcLocations = AFCLocationUtils.sort(afcMeasurement);
 
-            List<HistoricalLocation> locations = Converter.convertToHistoricalLocation(afcMultiSensor, afcLocations.getList(), uriComponent);
-            ctx.response().end(encode(locations));
+                List<HistoricalLocation> locations = Converter.convertToHistoricalLocation(afcMultiSensor, afcLocations.getList(), uriComponent);
+                ctx.response().end(encode(locations));
+            }
+        });
+
+        router().post(create("Observations")).handler(ctx -> {
+            logger.info("Handling a request: {}.", ctx.request().absoluteURI());
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON);
+
+            JsonObject bodyJson = ctx.getBodyAsJson();
+            if (bodyJson.isEmpty()) {
+                ctx.response().setStatusCode(204).send(); return;
+            }
+            try {
+                ObservationInsert ogcObservation = ObservationInsert.parse(bodyJson);
+                MultiSimpleObservation afcObservation = Converter.convertToMultiSimpleObservation(ogcObservation);
+                client.uploadAggregatedMeasurements(singletonList(afcObservation));
+            } catch (RuntimeException e) {
+                ctx.response().setStatusCode(501).end(new JsonObject()
+                        .put("message", e.getMessage()).encode()); return;
+            }
+            ctx.response().setStatusCode(201).send();
         });
+
+//        router().post(create("Things")).handler(ctx -> {
+//            logger.info("Handling a request: {}.", ctx.request().absoluteURI());
+//            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON);
+//
+//            ThingInsert ogcThing = ThingInsert.parse(ctx.getBodyAsJson());
+//            MultiSensor afcMultiSensor = Converter.convertToMultiSensor(ogcThing);
+//
+//            ctx.response().end(Json.encode(afcMultiSensor));
+//        });
     }
 
     private static class AFCAggrLocation implements Comparable<AFCAggrLocation> {
@@ -579,7 +637,7 @@ public class OGCSensorThingsGateway extends AbstractGateway {
         static String assemblyId(String... parts) {
             return String.join(DELIMITER, parts);
         }
-        
+
         static String[] disassemblyId(String id) {
             return id.split(DELIMITER);
         }
@@ -603,11 +661,11 @@ public class OGCSensorThingsGateway extends AbstractGateway {
                                 afcFilter.startTime(Instant.parse(expression.getValue()));
                             }break;
                             default: throw new IllegalArgumentException(format(
-                                        "Unsupported operator '%s' in the filter expression '%s'.", expression.getOperator(), ogcFilter));
+                                    "Unsupported operator '%s' in the filter expression '%s'.", expression.getOperator(), ogcFilter));
                         }
                     } break;
                     default: throw new IllegalArgumentException(format(
-                                "Unsupported attribute '%s' in the filter expression '%s'.", expression.getAttribute(), ogcFilter));
+                            "Unsupported attribute '%s' in the filter expression '%s'.", expression.getAttribute(), ogcFilter));
                 }
             }
 
@@ -645,7 +703,7 @@ public class OGCSensorThingsGateway extends AbstractGateway {
             location.setDescription(afcLocation.observation.getProvider());
             location.setEncodingType("application/vnd.geo+json");
 
-            Location.Info info = new Location.Info();
+            LocationInfo info = new LocationInfo();
             info.setType("Feature");
             location.setLocation(info);
 
@@ -741,7 +799,7 @@ public class OGCSensorThingsGateway extends AbstractGateway {
             datastream.setName(afcSensor.getObservedProperty());
             datastream.setDescription(afcSensor.getObservedProperty());
 
-            Datastream.UnitOfMeasurement uom = new Datastream.UnitOfMeasurement();
+            UnitOfMeasurement uom = new UnitOfMeasurement();
             uom.setName(afcSensor.getObservedProperty());
             uom.setSymbol("");
             uom.setDefinition(afcSensor.getUom());
@@ -802,5 +860,169 @@ public class OGCSensorThingsGateway extends AbstractGateway {
 
             return sensor;
         }
+
+        static MultiSimpleObservation convertToMultiSimpleObservation(ObservationInsert ogcObservation) {
+            String [] idPartsAfc = disassemblyId(ogcObservation.getDatastreamId());
+            String resourceUrn = idPartsAfc[0], observedProperty = idPartsAfc[1];
+            String [] urnPartsAfc = resourceUrn.split(":");
+            String resourceId = urnPartsAfc[urnPartsAfc.length-1];
+
+            MultiSimpleObservation multiSimpleObservation = new MultiSimpleObservation();
+            multiSimpleObservation.setResourceId(resourceId);
+
+            SimpleObservation simpleObservation = new SimpleObservation();
+            simpleObservation.setObservedProperty(observedProperty);
+            simpleObservation.setResultTime(ogcObservation.getPhenomenonTime().toEpochSecond());
+            simpleObservation.setResult(ogcObservation.getResult());
+
+            multiSimpleObservation.setObservations(singletonList(simpleObservation));
+
+            return multiSimpleObservation;
+        }
+
+        static MultiSensor convertToMultiSensor(ThingInsert ogcThing) {
+            String afcResourceType = ogcThing.getName();
+            if (!afcResourceType.equals(ogcThing.getDescription())) {
+                throw new IllegalArgumentException("Attribute 'description' has to be equal with 'name'. Contains 'resourceType' attribute from AFC.");
+            }
+            if (ogcThing.getLocations().size() != 1) {
+                throw new IllegalArgumentException("Attribute 'Locations' has to contain one location.");
+            }
+            Location ogcLocation = ogcThing.getLocations().get(0);
+            if (!afcResourceType.equals(ogcLocation.getName())) {
+                throw new IllegalArgumentException("Attribute 'Location/name' has to be equal with 'name'. Contains 'resourceType attribute from AFC.");
+            }
+            LocationInfo ogcLocationInfo = ogcLocation.getLocation();
+            String ogcLocationInfoType = ogcLocationInfo.getType();
+            if (!ogcLocationInfoType.equals("Feature")) {
+                throw new IllegalArgumentException("Allowed only 'Feature' type of location.");
+            }
+            Geometry ogcLocationGeometry = ogcLocationInfo.getGeometry();
+            if (!ogcLocationGeometry.getType().equals("Point")) {
+                throw new IllegalArgumentException("Allowed only 'Point' type of geometry.");
+            }
+            List<Double> ogcCoordinates = ogcLocationGeometry.getCoordinates();
+            if (ogcCoordinates.size() != 3) {
+                throw new IllegalArgumentException("Coordinates of location have to be in following format [longitude, latitude, altitude].");
+            }
+
+            double afcLongitude = ogcCoordinates.get(0);
+            double afcLatitude = ogcCoordinates.get(1);
+            double afcAltitude = ogcCoordinates.get(2);
+
+            List<DatastreamInsert> ogcDatastreams = ogcThing.getDatastreams();
+            List<MultiSensor.SensorSchema> afcSensorSchemas = new ArrayList<>(ogcDatastreams.size());
+            for (DatastreamInsert ogcDatastream : ogcDatastreams) {
+                String afcObservedProperty = ogcDatastream.getName();
+                if (!afcObservedProperty.equals(ogcDatastream.getDescription())) {
+                    throw new IllegalArgumentException("Attribute 'Datastream/description' has to be equal with 'Datastream/name'. Contains 'observedProperty attribute from AFC.");
+                }
+                UnitOfMeasurement ogcUnitOfMeasurement = ogcDatastream.getUnitOfMeasurement();
+                String afcUom = ogcUnitOfMeasurement.getDefinition();
+
+                String ogcObservationType = ogcDatastream.getObservationType();
+                if (!ogcObservationType.equals("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement")) {
+                    throw new IllegalArgumentException("For the attribute 'Datastream/observationType' is allowed following values [http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement].");
+                }
+
+                Geometry ogcObservedArea = ogcDatastream.getObservedArea();
+                if (!ogcObservedArea.getType().equals("Point")) {
+                    throw new IllegalArgumentException("Allowed only 'Point' type of observedArea for the datastream '"+afcObservedProperty+"'.");
+                }
+                List<Double> ogcObcAreaCoordinates = ogcObservedArea.getCoordinates();
+                if (ogcObcAreaCoordinates.size() != 3) {
+                    throw new IllegalArgumentException("Coordinates of location for the datastream '"+afcObservedProperty+"' have to be in following format [longitude, latitude, altitude].");
+                }
+                if (!(ogcObcAreaCoordinates.get(0).equals(afcLongitude) || ogcObcAreaCoordinates.get(1).equals(afcLatitude) || ogcObcAreaCoordinates.get(2).equals(afcAltitude))) {
+                    throw new IllegalArgumentException("Coordinates of the observedArea have to be same as the last location.");
+                }
+
+                ObservedProperty ogcObservedProperty = ogcDatastream.getObservedProperty();
+                if (!ogcObservedProperty.getName().equals(afcObservedProperty)) {
+                    throw new IllegalArgumentException("Name of ObservedProperty has to be same as the name of datastream.");
+                }
+                if (!ogcObservedProperty.getDescription().equals(afcObservedProperty)) {
+                    throw new IllegalArgumentException("Description of ObservedProperty has to be same as the name of the datastream.");
+                }
+                if (!ogcObservedProperty.getDefinition().equals(afcUom)) {
+                    throw new IllegalArgumentException("Definition of ObservedProperty has to be same as the definition of unit measurement.");
+                }
+
+                SensorInsert ogcSensor = ogcDatastream.getSensor();
+                if (!ogcSensor.getName().equals(afcResourceType)) {
+                    throw new IllegalArgumentException("Name of sensor has to be same as the name of thing.");
+                }
+                if (!ogcSensor.getDescription().equals(afcResourceType)) {
+                    throw new IllegalArgumentException("Description of sensor has to be same as the name of thing.");
+                }
+                if (!ogcSensor.getEncodingType().equals("application/json")) {
+                    throw new IllegalArgumentException("For the attribute 'datastream/sensor/encodingType is allowed only 'application/json' type.");
+                }
+
+                MultiSensor.SensorSchema afcSensor = new MultiSensor.SensorSchema();
+                afcSensor.setObservedProperty(afcObservedProperty);
+                afcSensor.setUom(afcUom);
+                afcSensor.setMin_value(Double.MIN_VALUE);   // TODO set min_value
+                afcSensor.setMax_value(Double.MAX_VALUE);   // TODO set max_value
+                afcSensor.setAccuracy(0.1);                 // TODO set accuracy
+                afcSensorSchemas.add(afcSensor);
+            }
+
+            if (ogcThing.getProperties() == null) {
+                final String[] propAttrs = new String[]{"resourceUrn","resourceId", "supportedProtocol", "hardwareVersion", "softwareVersion", "firmwareVersion", "preprocessing", "pythonScript"};
+                throw new IllegalArgumentException("Attribute 'properties' is required and has to contain following attributes " + Arrays.toString(propAttrs) + ".");
+            }
+            Map<String, Object> ogcProperties = ogcThing.getProperties();
+
+            String afcResourceId = (String) ogcProperties.get("resourceId");
+            if (afcResourceId == null) {
+                throw new IllegalArgumentException("Attribute 'properties/resourceId' is required.");
+            }
+            String afcResourceUrn = (String) ogcProperties.get("resourceUrn");
+            if (afcResourceUrn == null) {
+                throw new IllegalArgumentException("Attribute 'properties/resourceUrn' is required.");
+            }
+            String afcSupportedProtocol = (String) ogcProperties.get("supportedProtocol");
+            if (afcSupportedProtocol == null) {
+                throw new IllegalArgumentException("Attribute 'properties/supportedProtocol' is required.");
+            }
+            String afcHardwareVersion = (String) ogcProperties.get("hardwareVersion");
+            if (afcHardwareVersion == null) {
+                throw new IllegalArgumentException("Attribute 'properties/hardwareVersion' is required.");
+            }
+            String afcSoftwareVersion = (String) ogcProperties.get("softwareVersion");
+            if (afcSoftwareVersion == null) {
+                throw new IllegalArgumentException("Attribute 'properties/softwareVersion' is required.");
+            }
+            String afcFirmwareVersion = (String) ogcProperties.get("firmwareVersion");
+            if (afcFirmwareVersion == null) {
+                throw new IllegalArgumentException("Attribute 'properties/firmwareVersion' is required.");
+            }
+            String afcPythonScript = (String) ogcProperties.get("pythonScript");
+            if (afcPythonScript == null) {
+                throw new IllegalArgumentException("Attribute 'properties/pythonScript' is required.");
+            }
+            Boolean afcPreprocessing = (Boolean) ogcProperties.get("preprocessing");
+            if (afcPreprocessing == null) {
+                throw new IllegalArgumentException("Attribute 'properties/preprocessing' is required.");
+            }
+
+            MultiSensor afcMultiSensor = new MultiSensor();
+            afcMultiSensor.setResourceId(afcResourceId);
+            afcMultiSensor.setResourceType(afcResourceType);
+            afcMultiSensor.setResourceUrn(afcResourceUrn);
+            afcMultiSensor.setLongitude(afcLongitude);
+            afcMultiSensor.setLatitude(afcLatitude);
+            afcMultiSensor.setAltitude(afcAltitude);
+            afcMultiSensor.setPreprocessing(afcPreprocessing);
+            afcMultiSensor.setPythonScript(afcPythonScript);
+            afcMultiSensor.setObservations(afcSensorSchemas);
+            afcMultiSensor.setSupportedProtocol(afcSupportedProtocol);
+            afcMultiSensor.setHardwareVersion(afcHardwareVersion);
+            afcMultiSensor.setSoftwareVersion(afcSoftwareVersion);
+            afcMultiSensor.setFirmwareVersion(afcFirmwareVersion);
+
+            return afcMultiSensor;
+        }
     }
 }

+ 312 - 0
connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsAssertions.java

@@ -0,0 +1,312 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.module.afarcloud;
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class OGCSensorThingsAssertions {
+
+    private static JsonObject findSensorAFC(JsonArray afc, Map<String, String> properties) {
+        if (afc.size() == 1) {
+            return afc.getJsonObject(0);
+        } else {
+            for (int i = 0; i < afc.size(); i++) {
+                JsonObject sensor = afc.getJsonObject(i);
+                if (sensor.getString("resourceId").equalsIgnoreCase(properties.get("multiSensorId"))) {
+                    return sensor;
+                }
+            }
+        }
+        return new JsonObject();
+    }
+
+    private static JsonObject findSensorOGC(JsonArray ogc, Map<String, String> properties) {
+        if (ogc.size() == 1) {
+            return ogc.getJsonObject(0);
+        } else {
+            for (int i = 0; i < ogc.size(); i++) {
+                JsonObject sensor = ogc.getJsonObject(i);
+                if (sensor.getString("@iot.id").contains(properties.get("multiSensorId"))) {
+                    return sensor;
+                }
+            }
+        }
+        return new JsonObject();
+    }
+
+    private static JsonObject findResourceAFC(JsonArray resourcesAFC, Map<String, String> properties) {
+        for (int i = 0; i < resourcesAFC.size(); i++) {
+            JsonObject resource = resourcesAFC.getJsonObject(i);
+            if (resource.getString("resource").equalsIgnoreCase(properties.get("multiSensorId"))) {
+                return resource;
+            }
+        }
+        return new JsonObject();
+    }
+
+    private static JsonObject findSensorInfoAFC(String observedProperty, JsonArray observationsAFC) {
+        for (int i = 0; i < observationsAFC.size(); i++) {
+            JsonObject observationAFC = observationsAFC.getJsonObject(i);
+            String observedPropertyAFC = observationAFC.getString("observedProperty");
+            if (observedPropertyAFC.equals(observedProperty)) {
+                return observationAFC;
+            }
+        }
+        return new JsonObject();
+    }
+
+    private static JsonObject findMeasurementAFC(String observedProperty, JsonArray measurements) {
+        for (int i = 0; i < measurements.size(); i++) {
+            JsonObject measurement = measurements.getJsonObject(i);
+            if (measurement.getString("measurement").equals(observedProperty)) {
+                return measurement;
+            }
+        }
+        return new JsonObject();
+    }
+
+    public static void assertInfo(String json, Map<String, String> properties) {
+        JsonArray deployedModules = new JsonArray(json);
+        JsonObject afcModule = null;
+        for (int i = 0; i < deployedModules.size(); i++) {
+            JsonObject module = deployedModules.getJsonObject(i);
+            if (module.getString("id", "").equals(properties.get("module_id"))) {
+                afcModule = module; break;
+            }
+        }
+        assertNotNull(afcModule, String.format("Module '%s' is not deployed.", properties.get("module_id")));
+        String ogcGateway = null;
+        JsonArray gateways = afcModule.getJsonArray("gateways", new JsonArray());
+        for (int i = 0; i < gateways.size(); i++) {
+            String gateway = gateways.getString(i);
+            if (gateway.equals(properties.get("gateway_id"))) {
+                ogcGateway = gateway; break;
+            }
+        }
+        assertNotNull(ogcGateway, String.format("Gateway '%s' is not awailable.", properties.get("gateway_id")));
+    }
+
+    public static void assertThings(String jsonAFC, String jsonOGC, Map<String, String> properties) {
+        JsonObject sensorAFC = findSensorAFC(new JsonArray(jsonAFC), properties);
+        JsonObject sensorOGC = findSensorOGC(new JsonArray(jsonOGC), properties);
+
+        assertEquals(sensorAFC.getString("resourceUrn"), sensorOGC.getString("@iot.id"));
+        assertEquals(sensorAFC.getString("resourceType"), sensorOGC.getString("name"));
+        assertEquals(sensorAFC.getString("resourceType"), sensorOGC.getString("description"));
+    }
+    
+    public static void assertSensor(String jsonAFC, String jsonOGC, Map<String, String> properties) {
+        JsonObject sensorAFC = new JsonObject(jsonAFC);
+        JsonObject sensorOGC = new JsonObject(jsonOGC);
+
+        assertEquals(sensorAFC.getString("resourceUrn"), sensorOGC.getString("@iot.id"));
+        assertEquals(sensorAFC.getString("resourceType"), sensorOGC.getString("name"));
+        assertEquals(sensorAFC.getString("resourceType"), sensorOGC.getString("description"));
+
+    }
+
+    public static void assertDataStream(String jsonAFCSensor, String jsonAFCData, String jsonOGC, Map<String, String> properties) {
+
+        JsonObject sensorAFC = new JsonObject(jsonAFCSensor);
+        JsonArray observationsAFC = sensorAFC.getJsonArray("observations");
+
+        JsonObject dataAFC = new JsonObject(jsonAFCData);
+        JsonArray resourcesAFC = dataAFC.getJsonObject("results").getJsonArray("resources");
+
+        JsonObject resourceAFC = findResourceAFC(resourcesAFC, properties);
+        JsonArray measurements = resourceAFC.getJsonArray("measurements");
+
+        JsonArray dataStreamOGC = new JsonArray(jsonOGC);
+
+        String resourceUrnAFC = sensorAFC.getString("resourceUrn");
+        for (int i = 0; i < dataStreamOGC.size(); i++) {
+            JsonObject dataStream = dataStreamOGC.getJsonObject(i);
+            String id = dataStream.getString("@iot.id");
+            String [] idParts = id.split("&");
+            String resourceUrnOGC = idParts[0];
+            String observedPropertyOGC = idParts[1];
+            assertEquals(resourceUrnAFC, resourceUrnOGC);
+
+            JsonObject sensorInfoAFC = findSensorInfoAFC(observedPropertyOGC, observationsAFC);
+            String observedPropertyAFC = sensorInfoAFC.getString("observedProperty");
+            assertEquals(observedPropertyAFC, observedPropertyOGC);
+            assertEquals(observedPropertyAFC, dataStream.getString("name"));
+            assertEquals(observedPropertyAFC, dataStream.getString("description"));
+
+            JsonObject unitOfMeasurementOGC = dataStream.getJsonObject("unitOfMeasurement");
+            assertEquals(observedPropertyAFC, unitOfMeasurementOGC.getString("name"));
+
+            JsonObject measurementAFC = findMeasurementAFC(observedPropertyOGC, measurements);
+            JsonArray afc = measurementAFC.getJsonArray("observations");
+            JsonObject observationAFC = afc.size() == 1 ? afc.getJsonObject(0) : new JsonObject();
+
+            String uomAFC = observationAFC.getString("uom");
+            String definitionOGC = unitOfMeasurementOGC.getString("definition");
+            assertEquals(uomAFC, definitionOGC);
+
+            String observationTypeOGC = dataStream.getString("observationType");
+            assertEquals("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement", observationTypeOGC);
+
+            JsonObject observedAreaOGC = dataStream.getJsonObject("observedArea");
+            assertEquals("Point", observedAreaOGC.getString("type"));
+            JsonArray coordinatesOGC = observedAreaOGC.getJsonArray("coordinates");
+            Integer latitudeAFC = observationAFC.getInteger("latitude");
+            Integer longitudeAFC = observationAFC.getInteger("longitude");
+            assertEquals(longitudeAFC, coordinatesOGC.getInteger(0));
+            assertEquals(latitudeAFC, coordinatesOGC.getInteger(1));
+
+            String timeAFC = observationAFC.getString("time");
+            String phenomenonTimeOGC = dataStream.getString("phenomenonTime");
+            String [] phTimeParts = phenomenonTimeOGC.split("/");
+            assertEquals("<none>", phTimeParts[0]);
+            assertEquals(timeAFC, phTimeParts[1]);
+
+            String resultTimeOGC = dataStream.getString("resultTime");
+            String [] rsTimeParts = resultTimeOGC.split("/");
+            assertEquals("<none>", rsTimeParts[0]);
+            assertEquals(timeAFC, rsTimeParts[1]);
+        }
+    }
+    
+    public static void assertObservedProperty(String jsonAFCSensor, String jsonAFCData, String jsonOGC, Map<String, String> properties) {
+        JsonObject sensorAFC = new JsonObject(jsonAFCSensor);
+        JsonArray observationsAFC = sensorAFC.getJsonArray("observations");
+
+        JsonObject dataAFC = new JsonObject(jsonAFCData);
+        JsonArray resourcesAFC = dataAFC.getJsonObject("results").getJsonArray("resources");
+
+        JsonObject resourceAFC = findResourceAFC(resourcesAFC, properties);
+        JsonArray measurements = resourceAFC.getJsonArray("measurements");
+
+        JsonObject observedPropertyOGC = new JsonObject(jsonOGC);
+        String idOGC = observedPropertyOGC.getString("@iot.id");
+        String [] idParts = idOGC.split("&");
+        String resourceUrnOGC = idParts[0];
+        String propertyOGC = idParts[1];
+
+        JsonObject sensorInfoAFC = findSensorInfoAFC(propertyOGC, observationsAFC);
+
+        assertEquals(sensorAFC.getString("resourceUrn"), resourceUrnOGC);
+
+        String observedPropertyAFC = sensorInfoAFC.getString("observedProperty");
+        assertEquals(observedPropertyAFC, propertyOGC);
+        assertEquals(observedPropertyAFC, observedPropertyOGC.getString("name"));
+        assertEquals(observedPropertyAFC, observedPropertyOGC.getString("description"));
+
+        JsonObject measurementAFC = findMeasurementAFC(propertyOGC, measurements);
+        JsonArray afc = measurementAFC.getJsonArray("observations");
+        JsonObject observationAFC = afc.size() == 1 ? afc.getJsonObject(0) : new JsonObject();
+        assertEquals(observationAFC.getString("uom"), observedPropertyOGC.getString("definition"));
+    }
+
+    public static void assertObservation(String jsonAFCData, String jsonOGC, Map<String, String> properties) {
+        JsonObject dataAFC = new JsonObject(jsonAFCData);
+        JsonArray resourcesAFC = dataAFC.getJsonObject("results").getJsonArray("resources");
+
+        JsonObject resourceAFC = findResourceAFC(resourcesAFC, properties);
+
+        JsonArray observationsOGC = new JsonArray(jsonOGC);
+        JsonObject observationOGC = observationsOGC.size() == 1 ? observationsOGC.getJsonObject(0) : new JsonObject();
+        String idOGC = observationOGC.getString("@iot.id");
+        String [] idParts = idOGC.split("&");
+        String resourceOGC = idParts[0];
+        String measurementOGC = idParts[1];
+        String timeOGC = idParts[2];
+
+        assertEquals(resourceAFC.getString("resource"), resourceOGC);
+
+        JsonObject measurementAFC = findMeasurementAFC(measurementOGC, resourceAFC.getJsonArray("measurements"));
+        assertEquals(measurementAFC.getString("measurement"), measurementOGC);
+
+        JsonArray observationsAFC = measurementAFC.getJsonArray("observations");
+        JsonObject observationAFC = observationsAFC.size() == 1 ? observationsAFC.getJsonObject(0) : new JsonObject();
+        String timeAFC = observationAFC.getString("time");
+        assertEquals(timeAFC, timeOGC);
+        assertEquals(timeAFC, observationOGC.getString("phenomenonTime"));
+        assertEquals(timeAFC, observationOGC.getString("resultTime"));
+        assertEquals(observationAFC.getDouble("value"), observationOGC.getDouble("result"));
+
+    }
+    
+    public static void assertLocations(String jsonAFCSensor, String jsonAFCData, String jsonOGC, Map<String, String> properties) {
+        JsonObject sensorAFC = new JsonObject(jsonAFCSensor);
+
+        JsonArray locationsOGC = new JsonArray(jsonOGC);
+        JsonObject locationOGC = locationsOGC.size() == 1 ? locationsOGC.getJsonObject(0) : new JsonObject();
+
+        JsonObject dataAFC = new JsonObject(jsonAFCData);
+        JsonArray resourcesAFC = dataAFC.getJsonObject("results").getJsonArray("resources");
+        JsonObject resourceAFC = findResourceAFC(resourcesAFC, properties);
+
+        String idOGC = locationOGC.getString("@iot.id");
+        String [] idParts = idOGC.split("&");
+        String resourceUrnOGC = idParts[0];
+        String observedPropertyOGC = idParts[1];
+        String timeOGC = idParts[2];
+
+        JsonArray observationsAFC = sensorAFC.getJsonArray("observations");
+        JsonObject sensorInfoAFC = findSensorInfoAFC(observedPropertyOGC, observationsAFC);
+
+        assertEquals(sensorAFC.getString("resourceUrn"), resourceUrnOGC);
+        assertEquals(sensorInfoAFC.getString("observedProperty"), observedPropertyOGC);
+        assertEquals(sensorAFC.getString("resourceType"), locationOGC.getString("name"));
+
+        JsonObject geoLocationOGC = locationOGC.getJsonObject("location");
+        assertEquals("Feature", geoLocationOGC.getString("type"));
+
+        JsonObject geometryOGC = geoLocationOGC.getJsonObject("geometry");
+        assertEquals("Point", geometryOGC.getString("type"));
+
+        JsonArray coordinatesOGC = geometryOGC.getJsonArray("coordinates");
+        assertEquals(sensorAFC.getDouble("longitude"), coordinatesOGC.getDouble(0));
+        assertEquals(sensorAFC.getDouble("latitude"), coordinatesOGC.getDouble(1));
+        assertEquals(sensorAFC.getDouble("altitude"), coordinatesOGC.getDouble(2));
+
+        JsonObject measurementAFC = findMeasurementAFC(observedPropertyOGC, resourceAFC.getJsonArray("measurements"));
+        assertEquals(measurementAFC.getString("measurement"), observedPropertyOGC);
+
+        JsonArray observationsDataAFC = measurementAFC.getJsonArray("observations");
+        JsonObject observationDataAFC = observationsDataAFC.size() == 1 ? observationsDataAFC.getJsonObject(0) : new JsonObject();
+        assertEquals(observationDataAFC.getString("time"), timeOGC);
+        assertEquals(observationDataAFC.getString("provider"), locationOGC.getString("description"));
+    }
+
+    public static void assertHistoricalLocation(String jsonAFCSensor, String jsonAFCData, String jsonOGC, Map<String, String> properties) {
+        JsonObject sensorAFC = new JsonObject(jsonAFCSensor);
+
+        JsonArray hisLocsOGC = new JsonArray(jsonOGC);
+        JsonObject hisLocationOGC = hisLocsOGC.size() == 1 ? hisLocsOGC.getJsonObject(0) : new JsonObject();
+
+        JsonObject dataAFC = new JsonObject(jsonAFCData);
+        JsonArray resourcesAFC = dataAFC.getJsonObject("results").getJsonArray("resources");
+        JsonObject resourceAFC = findResourceAFC(resourcesAFC, properties);
+
+        String idOGC = hisLocationOGC.getString("@iot.id");
+        String [] idParts = idOGC.split("&");
+        String resourceUrnOGC = idParts[0];
+        String observedPropertyOGC = idParts[1];
+        String timeOGC = idParts[2];
+
+        JsonArray observationsAFC = sensorAFC.getJsonArray("observations");
+        JsonObject sensorInfoAFC = findSensorInfoAFC(observedPropertyOGC, observationsAFC);
+
+        assertEquals(sensorAFC.getString("resourceUrn"), resourceUrnOGC);
+        assertEquals(sensorInfoAFC.getString("observedProperty"), observedPropertyOGC);
+
+        JsonObject measurementAFC = findMeasurementAFC(observedPropertyOGC, resourceAFC.getJsonArray("measurements"));
+        assertEquals(measurementAFC.getString("measurement"), observedPropertyOGC);
+
+        JsonArray observationsDataAFC = measurementAFC.getJsonArray("observations");
+        JsonObject observationDataAFC = observationsDataAFC.size() == 1 ? observationsDataAFC.getJsonObject(0) : new JsonObject();
+        String timeAFC = observationDataAFC.getString("time");
+        assertEquals(timeAFC, timeOGC);
+        assertEquals(timeAFC, hisLocationOGC.getString("time"));
+    }
+}

+ 94 - 0
connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsOfflineIntegrationTest.java

@@ -0,0 +1,94 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.module.afarcloud;
+
+import io.connector.test.api.TestType;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import static io.connector.module.afarcloud.OGCSensorThingsAssertions.*;
+import static java.nio.file.Files.readAllBytes;
+import static java.nio.file.Paths.get;
+import static java.util.Objects.requireNonNull;
+
+@Tag(TestType.INTEGRATION)
+class OGCSensorThingsOfflineIntegrationTest {
+
+    private static final Map<String, String> DEFAULT_PROPERTIES;
+
+    static {
+        DEFAULT_PROPERTIES = new HashMap<>();
+
+        DEFAULT_PROPERTIES.put("multiSensorId", "10002222");
+    }
+
+    private String readResourceFile(String relativePath) throws IOException {
+        try {
+            URL url = getClass().getClassLoader().getResource(relativePath);
+            byte[] bytes = readAllBytes(get(requireNonNull(url).toURI()));
+            return new String(bytes);
+        } catch (URISyntaxException e) {
+            throw new IOException(e);
+        }
+    }
+
+    @Test
+    void things() throws IOException {
+        String jsonAFCAllSensors = readResourceFile("afc/getAllSensors.json");
+        String jsonOGCThings = readResourceFile("ogc/things.json");
+        assertThings(jsonAFCAllSensors, jsonOGCThings, DEFAULT_PROPERTIES);
+    }
+
+    @Test
+    void sensor() throws IOException {
+        String jsonAFCSensor = readResourceFile("afc/getSensor.json");
+        String jsonOGCDataStream = readResourceFile("ogc/sensor.json");
+        assertSensor(jsonAFCSensor, jsonOGCDataStream, DEFAULT_PROPERTIES);
+    }
+
+    @Test
+    void dataStreams() throws IOException {
+        String jsonAFCSensor = readResourceFile("afc/getSensor.json");
+        String jsonAFCData = readResourceFile("afc/getObservationsBySensor.json");
+        String jsonOGC = readResourceFile("ogc/datastreams.json");
+        assertDataStream(jsonAFCSensor, jsonAFCData, jsonOGC, DEFAULT_PROPERTIES);
+    }
+
+    @Test
+    void observationProperty() throws IOException {
+        String jsonAFCSensor = readResourceFile("afc/getSensor.json");
+        String jsonAFCData = readResourceFile("afc/getObservationsBySensor.json");
+        String jsonOGC = readResourceFile("ogc/observationProperty.json");
+        assertObservedProperty(jsonAFCSensor, jsonAFCData, jsonOGC, DEFAULT_PROPERTIES);
+    }
+
+    @Test
+    void observations() throws IOException {
+        String jsonAFCData = readResourceFile("afc/getObservationsBySensor.json");
+        String jsonOGC = readResourceFile("ogc/observations.json");
+        assertObservation(jsonAFCData, jsonOGC, DEFAULT_PROPERTIES);
+    }
+
+    @Test
+    void locations() throws IOException {
+        String jsonAFCSensor = readResourceFile("afc/getSensor.json");
+        String jsonAFCData = readResourceFile("afc/getObservationsBySensor.json");
+        String jsonOGC = readResourceFile("ogc/locations.json");
+        assertLocations(jsonAFCSensor, jsonAFCData, jsonOGC, DEFAULT_PROPERTIES);
+    }
+
+    @Test
+    void historicalLocations() throws IOException {
+        String jsonAFCSensor = readResourceFile("afc/getSensor.json");
+        String jsonAFCData = readResourceFile("afc/getObservationsBySensor.json");
+        String jsonOGC = readResourceFile("ogc/historicalLocations.json");
+        assertHistoricalLocation(jsonAFCSensor, jsonAFCData, jsonOGC, DEFAULT_PROPERTIES);
+    }
+}

+ 320 - 0
connector-module-afarcloud/src/test/java/io/connector/module/afarcloud/OGCSensorThingsOnlineIntegrationTest.java

@@ -0,0 +1,320 @@
+// Copyright (c) 2020 UWB & LESP.
+// The UWB & LESP license this file to you under the MIT license.
+
+package io.connector.module.afarcloud;
+
+import io.connector.core.*;
+import io.connector.core.config.DefaultConfig;
+import io.connector.core.http.RequestUriComponent;
+import io.connector.core.http.RequestUriParser;
+import io.connector.test.api.TestType;
+import io.vertx.core.*;
+import io.vertx.core.http.HttpClientOptions;
+import io.vertx.core.http.HttpClientResponse;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+import io.vertx.core.http.HttpClient;
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.*;
+import java.util.function.Function;
+
+import static io.connector.core.AddressPath.Creator.create;
+import static io.connector.module.afarcloud.OGCSensorThingsAssertions.*;
+import static io.vertx.core.http.HttpMethod.GET;
+import static java.util.Collections.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+@Tag(TestType.INTEGRATION)
+@ExtendWith(VertxExtension.class)
+class OGCSensorThingsOnlineIntegrationTest {
+
+    private static class HttpHostInfo {
+        private final HttpClientOptions options;
+        private final String pathPrefix;
+
+        private static HttpHostInfo create(int port, String host, String pathPrefix, boolean ssl) {
+            return new HttpHostInfo(port, host, pathPrefix, ssl);
+        }
+
+        private HttpHostInfo(int port, String host, String pathPrefix, boolean ssl) {
+            this.options = new HttpClientOptions().setSsl(ssl).setDefaultPort(port).setDefaultHost(host);
+            this.pathPrefix = pathPrefix;
+        }
+    }
+
+    private static final int JUNIT_MAX_WALKTHROUGH = 7;
+
+    private static final String API_PREFIX = "/test";
+    private static final int DEFAULT_PORT = 9999;
+    private static final String OGC_JSON_KEY = "ogc";
+
+    private static final String MODULE_ID = "AFarCloud";
+    private static final String GATEWAY_ID = "OGCSensorThings";
+
+    private static final String DEFAULT_OGC_THING_ID = "urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222";
+    private static final String DEFAULT_AFC_SENSOR_ID = "10002222";
+
+    private static final Map<String, String> DEFAULT_PROPERTIES = new HashMap<String, String>(){{
+        put("multiSensorId", DEFAULT_AFC_SENSOR_ID);
+    }};
+
+    private static final HttpHostInfo OGC_HOST = HttpHostInfo.create(DEFAULT_PORT, "localhost", create(API_PREFIX, MODULE_ID, GATEWAY_ID), false);
+
+    private static final HttpHostInfo AFC_RETRIEVAL_HOST = HttpHostInfo.create(9219, "torcos.etsist.upm.es", "", false);
+    private static final HttpHostInfo AFC_TELEMETRY_HOST = HttpHostInfo.create(9207, "torcos.etsist.upm.es", "", true);
+    private static final HttpHostInfo AFC_INFO_HOST = HttpHostInfo.create(443, "storage07-afarcloud.qa.pdmfc.com", "/storage/rest", true);
+
+    private static class TestConfig {
+        private final RequestConfig[] afcRequestConfigs;
+        private final Function<Map<String, String>, Map<String, String>> handler;
+
+        public TestConfig(RequestConfig[] afcRequestConfigs, Function<Map<String, String>, Map<String, String>> handler) {
+            this.afcRequestConfigs = afcRequestConfigs;
+            this.handler = handler;
+        }
+    }
+
+    private static class RequestConfig {
+        private final String key, path;
+        private final HttpHostInfo hostInfo;
+
+        private RequestConfig(String key, HttpHostInfo hostInfo, String path) {
+            this.key = key;
+            this.hostInfo = hostInfo;
+            this.path = create(hostInfo.pathPrefix, path);
+        }
+    }
+
+    private static final Map<String, TestConfig> TEST_CONFIGS;
+
+    private static final Stack<Map.Entry<String, String>> testGraphWalk;
+    private static final Set<String> testVisitedNodes;
+
+
+    static {
+        TEST_CONFIGS = new HashMap<>();
+
+        // configuration for /Things => []
+        TEST_CONFIGS.put("Things", new TestConfig(new RequestConfig[]{
+                new RequestConfig("afcSensors", AFC_INFO_HOST, "registry/getAllSensors"),
+        }, results -> {
+            String afcSensors = results.get("afcSensors");
+            String ogcThings = results.get(OGC_JSON_KEY);
+            assertThings(afcSensors, ogcThings, DEFAULT_PROPERTIES);
+            return findNextNavigationLinks(ogcThings, DEFAULT_OGC_THING_ID);
+        }));
+
+        // configuration for /Locations => []
+        TEST_CONFIGS.put("Locations", new TestConfig(new RequestConfig [] {
+                new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
+                new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
+        }, results -> {
+            String afcSensors = results.get("afcSensor");
+            String afcData = results.get("afcData");
+            String ogcLocations = results.get(OGC_JSON_KEY);
+            assertLocations(afcSensors, afcData, ogcLocations, DEFAULT_PROPERTIES);
+            return findNextNavigationLinks(ogcLocations, 0);
+        }));
+
+        // configuration for /HistoricalLocations => {}
+        TEST_CONFIGS.put("HistoricalLocations", new TestConfig(new RequestConfig[]{
+                new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
+                new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
+        }, results -> {
+            String afcSensors = results.get("afcSensor");
+            String afcData = results.get("afcData");
+            String ogcHistoricalLocations = results.get(OGC_JSON_KEY);
+            assertHistoricalLocation(afcSensors, afcData, ogcHistoricalLocations, DEFAULT_PROPERTIES);
+            return emptyMap();
+        }));
+
+        // configuration for /Datastreams => []
+        TEST_CONFIGS.put("Datastreams", new TestConfig(new RequestConfig[]{
+                new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
+                new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
+        }, results -> {
+            String afcSensors = results.get("afcSensor");
+            String afcData = results.get("afcData");
+            String ogcDatastreams = results.get(OGC_JSON_KEY);
+            assertDataStream(afcSensors, afcData, ogcDatastreams, DEFAULT_PROPERTIES);
+            return findNextNavigationLinks(ogcDatastreams, 0);
+        }));
+
+        // configuration for /Sensor => {}
+        TEST_CONFIGS.put("Sensor", new TestConfig(new RequestConfig[]{
+                new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID))
+        }, results -> {
+            String afcSensors = results.get("afcSensor");
+            String ogcSensor = results.get(OGC_JSON_KEY);
+            assertSensor(afcSensors, ogcSensor, DEFAULT_PROPERTIES);
+            return emptyMap();
+        }));
+
+        // configuration for /ObservedProperty => {}
+        TEST_CONFIGS.put("ObservedProperty", new TestConfig(new RequestConfig[]{
+                new RequestConfig("afcSensor", AFC_INFO_HOST, create("registry/getSensor", DEFAULT_AFC_SENSOR_ID)),
+                new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
+        }, results -> {
+            String afcSensors = results.get("afcSensor");
+            String afcData = results.get("afcData");
+            String ogcObservedProperty = results.get(OGC_JSON_KEY);
+            assertObservedProperty(afcSensors, afcData, ogcObservedProperty, DEFAULT_PROPERTIES);
+            return emptyMap();
+        }));
+
+        // configuration for /Observations => []
+        TEST_CONFIGS.put("Observations", new TestConfig(new RequestConfig[]{
+                new RequestConfig("afcData", AFC_RETRIEVAL_HOST, "getObservationsBySensor/latest"),
+        }, results -> {
+            String afcData = results.get("afcData");
+            String ogcObservations = results.get(OGC_JSON_KEY);
+            assertObservation(afcData, ogcObservations, DEFAULT_PROPERTIES);
+            return emptyMap();
+        }));
+
+        testVisitedNodes = new HashSet<>();
+        testGraphWalk = new Stack<>();
+        testGraphWalk.push(entryOf("Things", "Things"));
+    }
+
+    private static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
+        return new AbstractMap.SimpleEntry<>(key, value);
+    }
+
+    private static <K, V> Map<K, V> toMap(List<Map.Entry<K, V>> entries) {
+        Map<K, V> map = new HashMap<>(entries.size());
+        for (Map.Entry<K, V> entry : entries) {
+            map.put(entry.getKey(), entry.getValue());
+        }
+        return map;
+    }
+
+    private static Map<String, String> findNextNavigationLinks(String ogcJson, String id) {
+        JsonArray jsonArray = new JsonArray(ogcJson);
+        for (int i = 0; i < jsonArray.size(); i++) {
+            JsonObject jsonObject = jsonArray.getJsonObject(i);
+            if (jsonObject.getString("@iot.id", "").equals(id)) {
+                return findNextNavigationLinks(jsonObject);
+            }
+        }
+        return emptyMap();
+    }
+
+    private static Map<String, String> findNextNavigationLinks(String ogcJson, int pos) {
+        JsonArray jsonArray = new JsonArray(ogcJson);
+        if (jsonArray.size() < pos) return emptyMap();
+        return findNextNavigationLinks(jsonArray.getJsonObject(pos));
+    }
+
+    private static Map<String, String> findNextNavigationLinks(String ogcJson) {
+        return findNextNavigationLinks(new JsonObject(ogcJson));
+    }
+
+    private static Map<String, String> findNextNavigationLinks(JsonObject ogcJson) {
+        Map<String, String> links = new HashMap<>();
+        for (String fieldName : ogcJson.fieldNames()) {
+            if (fieldName.endsWith("@iot.navigationLink")) {
+                String type = fieldName.split("@")[0];
+                links.put(type, ogcJson.getString(fieldName));
+            }
+        }
+        return links;
+    }
+
+    @BeforeAll
+    @DisplayName("Deploy the module:" + MODULE_ID)
+    static void prepare(Vertx vertx, VertxTestContext testContext) {
+        DefaultConfig serviceConfig = new DefaultConfig(MODULE_ID, AFCModuleProvider.class);
+        serviceConfig.setProperty("retrievalApi", new HashMap<String, String>(){{
+            put("domain", String.format("%s://%s:%d", AFC_RETRIEVAL_HOST.options.isSsl() ? "https" : "http",
+                    AFC_RETRIEVAL_HOST.options.getDefaultHost(), AFC_RETRIEVAL_HOST.options.getDefaultPort())
+            );
+        }});
+        serviceConfig.setProperty("telemetryApi", new HashMap<String, String>(){{
+            put("domain", String.format("%s://%s:%d", AFC_TELEMETRY_HOST.options.isSsl() ? "https" : "http",
+                    AFC_TELEMETRY_HOST.options.getDefaultHost(), AFC_TELEMETRY_HOST.options.getDefaultPort())
+            );
+        }});
+        serviceConfig.setProperty("infoApi", new HashMap<String, String>(){{
+            put("domain", String.format("%s://%s/%s", AFC_INFO_HOST.options.isSsl() ? "https" : "http",
+                    AFC_INFO_HOST.options.getDefaultHost(), AFC_INFO_HOST.pathPrefix)
+            );
+        }});
+        ModuleDescriptor descriptor = new ModuleDescriptor(serviceConfig.getId(), serviceConfig, null);
+        AFCModuleProvider provider = new AFCModuleProvider();
+
+        JsonObject serverConfig = new JsonObject();
+        serverConfig.put("http.server.port", OGC_HOST.options.getDefaultPort());
+        serverConfig.put("http.server.prefix", API_PREFIX);
+
+        DeploymentOptions options = new DeploymentOptions().setConfig(serverConfig);
+        List<AbstractModule> modules = singletonList(provider.createModule(descriptor));
+        vertx.deployVerticle(ModuleDeployer.deploy(modules), options,  testContext.succeedingThenComplete());
+    }
+
+    @AfterEach
+    @DisplayName("Check that the verticle is still there")
+    void lastChecks(Vertx vertx) {
+        assertFalse(vertx.deploymentIDs().isEmpty());
+    }
+
+    @AfterAll
+    @DisplayName("Check visited nodes")
+    static void checkVisitedNodes() {
+        for (String nodeName : TEST_CONFIGS.keySet()) {
+            assertTrue(testVisitedNodes.contains(nodeName), "The node '"+nodeName+"' was not visited.");
+        }
+    }
+
+    @RepeatedTest(JUNIT_MAX_WALKTHROUGH)
+    @DisplayName("Testing endpoints.")
+    void test_endpointsWalkThrough(Vertx vertx, VertxTestContext testContext) {
+        if (testGraphWalk.isEmpty()) {
+            testContext.completeNow(); return;
+        }
+
+        Map.Entry<String, String> endpoint = testGraphWalk.pop();
+        TestConfig testConfig = TEST_CONFIGS.get(endpoint.getKey());
+        assertNotNull(testConfig, "No test configuration for the node '" + endpoint + "'.");
+
+        List<Future> futures = new ArrayList<>(testConfig.afcRequestConfigs.length);
+        for (RequestConfig requestConfig : testConfig.afcRequestConfigs) {
+            Promise<Map.Entry<String, String>> promise = Promise.promise();
+            futures.add(promise.future());
+            vertx.createHttpClient(requestConfig.hostInfo.options).request(GET, requestConfig.path)
+                    .compose(req -> req.send().compose(HttpClientResponse::body).onFailure(testContext::failNow))
+                    .onComplete(testContext.succeeding(buffer -> promise.complete(entryOf(requestConfig.key, buffer.toString()))))
+                    .onFailure(testContext::failNow);
+        }
+
+        Promise<Map.Entry<String, String>> promise = Promise.promise();
+        futures.add(promise.future());
+        vertx.createHttpClient(OGC_HOST.options).request(GET, create(OGC_HOST.pathPrefix, endpoint.getValue()))
+                .compose(req -> req.send().compose(HttpClientResponse::body).onFailure(testContext::failNow))
+                .onComplete(testContext.succeeding(buffer -> promise.complete(entryOf(OGC_JSON_KEY, buffer.toString()))))
+                .onFailure(testContext::failNow);
+
+        CompositeFuture.all(futures).onComplete(reply -> testContext.verify(() -> {
+                    Map<String, String> results = toMap(reply.result().list());
+                    // testing number of results from afc + ogc (1)
+                    assertEquals(testConfig.afcRequestConfigs.length + 1, results.size(), "Number of results from endpoints does not match.");
+                    Map<String, String> nextLinks = testConfig.handler.apply(results);
+                    testVisitedNodes.add(endpoint.getKey());
+                    for (Map.Entry<String, String> nextLink : nextLinks.entrySet()) {
+                        String nextLinkName = nextLink.getKey();
+                        if (TEST_CONFIGS.containsKey(nextLinkName) && !testVisitedNodes.contains(nextLinkName)) {
+                            RequestUriComponent uriComponent = RequestUriParser.parse(nextLink.getValue(), MODULE_ID, GATEWAY_ID);
+                            if (uriComponent != null) {
+                                testGraphWalk.push(entryOf(nextLinkName, uriComponent.getAddress()));
+                            }
+                        }
+                    }
+                    testContext.completeNow();
+                })
+        );
+    }
+}

+ 1 - 0
connector-module-afarcloud/src/test/resources/afc/getAllSensors.json

@@ -0,0 +1 @@
+[{"resourceId":"10002222","resourceType":"air_sensor","resourceUrn":"urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222","latitude":50.0382589,"longitude":14.6112164,"altitude":280.0,"preprocessing":false,"pythonScript":"","dataIntegrity":false,"observations":[{"observedProperty":"air_temperature","uom":"http://qudt.org/vocab/unit/DEG_C","min_value":-40.0,"max_value":100.0,"accuracy":0.1},{"observedProperty":"air_humidity","uom":"http://qudt.org/vocab/unit/PERCENT","min_value":0.0,"max_value":150.0,"accuracy":0.01},{"observedProperty":"battery","uom":"http://qudt.org/vocab/unit/V","min_value":0.0,"max_value":10.0,"accuracy":0.01}],"supportedProtocol":"REST","hardwareVersion":"1","softwareVersion":"1.0","firmwareVersion":"Rev.1"}]

+ 1 - 0
connector-module-afarcloud/src/test/resources/afc/getObservationsBySensor.json

@@ -0,0 +1 @@
+{"query":"/getObservationsBySensor/latest","params":{"limit":"1"},"numResults":26,"results":{"resources":[{"resource":"10002222","measurements":[{"measurement":"battery","observations":[{"time":"2020-10-27T14:53:06Z","altitude":280.0,"geohash":"u2fksmptfp89","latitude":50.0382589,"longitude":14.6112164,"value":3.359999895095825,"entityName":"10002222","service":"environmentalObservations","provider":"IMA","type":"air_sensor","uom":"http://qudt.org/vocab/unit/V"}]},{"measurement":"air_temperature","observations":[{"time":"2020-10-27T05:06:32Z","altitude":280.0,"geohash":"u2fksmptfp89","latitude":50.0382589,"longitude":14.6112164,"value":9.399999618530273,"entityName":"10002222","service":"environmentalObservations","provider":"IMA","type":"air_sensor","uom":"http://qudt.org/vocab/unit/DEG_C"}]},{"measurement":"air_humidity","observations":[{"time":"2020-10-27T14:53:06Z","altitude":280.0,"geohash":"u2fksmptfp89","latitude":50.0382589,"longitude":14.6112164,"value":78.47000122070312,"entityName":"10002222","service":"environmentalObservations","provider":"IMA","type":"air_sensor","uom":"http://qudt.org/vocab/unit/PERCENT"}]}]}]}}

+ 1 - 0
connector-module-afarcloud/src/test/resources/afc/getSensor.json

@@ -0,0 +1 @@
+{"resourceId":"10002222","resourceType":"air_sensor","resourceUrn":"urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222","latitude":50.0382589,"longitude":14.6112164,"altitude":280.0,"preprocessing":false,"pythonScript":"","dataIntegrity":false,"observations":[{"observedProperty":"air_temperature","uom":"http://qudt.org/vocab/unit/DEG_C","min_value":-40.0,"max_value":100.0,"accuracy":0.1},{"observedProperty":"air_humidity","uom":"http://qudt.org/vocab/unit/PERCENT","min_value":0.0,"max_value":150.0,"accuracy":0.01},{"observedProperty":"battery","uom":"http://qudt.org/vocab/unit/V","min_value":0.0,"max_value":10.0,"accuracy":0.01}],"supportedProtocol":"REST","hardwareVersion":"1","softwareVersion":"1.0","firmwareVersion":"Rev.1"}

File diff suppressed because it is too large
+ 0 - 0
connector-module-afarcloud/src/test/resources/ogc/datastreams.json


+ 1 - 0
connector-module-afarcloud/src/test/resources/ogc/historicalLocations.json

@@ -0,0 +1 @@
+[{"@iot.id":"urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature&2020-10-27T05:06:32Z","@iot.selfLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/HistoricalLocations(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature&2020-10-27T05:06:32Z)","Locations@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/HistoricalLocations(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature&2020-10-27T05:06:32Z)/Locations","Thing@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/HistoricalLocations(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature&2020-10-27T05:06:32Z)/Thing","time":"2020-10-27T05:06:32Z"}]

+ 1 - 0
connector-module-afarcloud/src/test/resources/ogc/locations.json

@@ -0,0 +1 @@
+[{"@iot.id":"urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature&2020-10-27T05:06:32Z","@iot.selfLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Locations(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature&2020-10-27T05:06:32Z)","HistoricalLocations@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Locations(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature&2020-10-27T05:06:32Z)/HistoricalLocations","name":"air_sensor","description":"IMA","encodingType":"application/vnd.geo+json","location":{"type":"Feature","geometry":{"type":"Point","coordinates":[14.6112164,50.0382589,280.0]}}}]

+ 1 - 0
connector-module-afarcloud/src/test/resources/ogc/observationProperty.json

@@ -0,0 +1 @@
+{"@iot.id":"urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature","@iot.selfLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/ObservedProperties(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature)","Datastreams@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/ObservedProperties(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222&air_temperature)/Datastream","name":"air_temperature","description":"air_temperature","definition":"http://qudt.org/vocab/unit/DEG_C"}

+ 1 - 0
connector-module-afarcloud/src/test/resources/ogc/observations.json

@@ -0,0 +1 @@
+[{"@iot.id":"10002222&air_temperature&2020-10-27T05:06:32Z","@iot.selfLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Observations(10002222&air_temperature&2020-10-27T05:06:32Z)","FeatureOfInterest@iot.navigationLink":"https://storage07-afarcloud.qa.pdmfc.com/storage/rest/registry/getAllObservationTypes","Datastreams@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Observations(10002222&air_temperature&2020-10-27T05:06:32Z)/Datastream","phenomenonTime":"2020-10-27T05:06:32Z","resultTime":"2020-10-27T05:06:32Z","result":9.399999618530273}]

+ 1 - 0
connector-module-afarcloud/src/test/resources/ogc/sensor.json

@@ -0,0 +1 @@
+{"@iot.id":"urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222","@iot.selfLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Sensors(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222)","Datastreams@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Sensors(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222)/Datastreams","name":"air_sensor","description":"air_sensor","encodingType":"application/json","metadata":"https://storage07-afarcloud.qa.pdmfc.com/storage/rest/registry/getSensor/10002222"}

+ 1 - 0
connector-module-afarcloud/src/test/resources/ogc/things.json

@@ -0,0 +1 @@
+[{"@iot.id":"urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222","@iot.selfLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Things(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222)","Locations@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Things(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222)/Locations","Datastreams@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Things(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222)/Datastreams","HistoricalLocations@iot.navigationLink":"http://127.0.0.1:8080/api/AFarCloud/OGCSensorThings/Things(urn:afc:AS07:environmentalObservations:IMA:air_sensor:10002222)/HistoricalLocations","name":"air_sensor","description":"air_sensor"}]

BIN
doc/OGCSensorThings_v1.1_navigationLinks.png


+ 16 - 0
docker-compose.yaml

@@ -0,0 +1,16 @@
+version: "3.9"
+
+services:
+
+  afc:
+    container_name: connector_ogc2afc
+    image: connector/ogc2afc
+    build:
+      target: production
+      context: .
+      args:
+        config_file: config/ogc2afc.yaml
+        module: connector-module-afarcloud
+    ports:
+      - "8080:8080"
+    restart: always

+ 0 - 14
docker/Dockerfile

@@ -1,14 +0,0 @@
-FROM java:8-alpine
-
-ARG config_file
-ARG port
-
-ENV APP_PARAMS "-cf config/$config_file -p $port"
-
-COPY docker/start.sh /app/
-COPY bin/libs/ /app/bin
-COPY config/$config_file /app/config/$config_file
-
-WORKDIR /app
-
-ENTRYPOINT ["/bin/sh", "-C", "start.sh"]

+ 0 - 7
docker/start.sh

@@ -1,7 +0,0 @@
-#!/bin/sh
-
-BUILD_FOLDER="bin"
-MAIN_CLASS="io.connector.Main"
-LOG_PATH="/var/log/connector-app"
-
-java -cp "$BUILD_FOLDER/*" -DlogPath=$LOG_PATH $MAIN_CLASS $APP_PARAMS

BIN
gradle/wrapper/gradle-6.5.1-bin.zip


BIN
gradle/wrapper/gradle-wrapper.jar


+ 3 - 3
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
-#Tue Jul 07 11:00:55 CEST 2020
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+#distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
+distributionUrl=gradle-6.5.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 1 - 4
settings.gradle

@@ -1,9 +1,6 @@
 rootProject.name = 'connector'
+include 'connector-app'
 include 'connector-core'
 include 'connector-model'
 include 'connector-module-afarcloud'
-include 'connector-module-ima'
-include 'connector-app'
-include 'connector-module-senslog1'
-include 'connector-module-ogc-sensorthings'
 

Some files were not shown because too many files changed in this diff