Explorar o código

added OGC SensorThings model + rest examples, prepared for /senslog/sensorThings mapping, turned off scheduling

Lukas Cerny %!s(int64=5) %!d(string=hai) anos
pai
achega
ecb187d5d7
Modificáronse 29 ficheiros con 1146 adicións e 81 borrados
  1. 5 1
      build.gradle
  2. 8 8
      config/test.yaml
  3. 105 10
      connector-core/src/main/java/io/connector/core/AbstractGateway.java
  4. 10 0
      connector-core/src/main/java/io/connector/core/Handler.java
  5. 4 1
      connector-core/src/main/java/io/connector/core/ModuleDeployer.java
  6. 1 1
      connector-core/src/main/java/io/connector/core/ModuleInfo.java
  7. 11 1
      connector-core/src/main/java/io/connector/core/config/PropertyConfig.java
  8. 48 0
      connector-core/src/main/java/io/connector/core/http/RequestUriComponent.java
  9. 15 0
      connector-core/src/main/java/io/connector/core/http/RequestUriParser.java
  10. 2 1
      connector-model/src/main/java/io/connector/model/senslog1/Observation.java
  11. 1 1
      connector-model/src/main/java/io/connector/model/senslog1/SensorData.java
  12. 1 1
      connector-model/src/main/java/io/connector/model/senslog1/UnitData.java
  13. 174 0
      connector-model/src/main/java/io/connector/model/sensorthings/Datastream.java
  14. 81 0
      connector-model/src/main/java/io/connector/model/sensorthings/FeatureOfInterest.java
  15. 25 0
      connector-model/src/main/java/io/connector/model/sensorthings/Geometry.java
  16. 74 0
      connector-model/src/main/java/io/connector/model/sensorthings/HistoricalLocation.java
  17. 89 0
      connector-model/src/main/java/io/connector/model/sensorthings/Location.java
  18. 62 0
      connector-model/src/main/java/io/connector/model/sensorthings/Observation.java
  19. 54 0
      connector-model/src/main/java/io/connector/model/sensorthings/ObservedProperty.java
  20. 62 0
      connector-model/src/main/java/io/connector/model/sensorthings/Sensor.java
  21. 72 0
      connector-model/src/main/java/io/connector/model/sensorthings/Think.java
  22. 1 2
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCClient.java
  23. 3 1
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCModule.java
  24. 2 3
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCModuleProvider.java
  25. 203 0
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java
  26. 11 4
      connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/SensLog1Gateway.java
  27. 5 0
      connector-module-senslog1/src/main/java/io/connector/module/senslog1/SensLog1HttpClient.java
  28. 1 2
      connector-module-senslog1/src/main/java/io/connector/module/senslog1/SensLog1Module.java
  29. 16 44
      connector-module-senslog1/src/main/java/io/connector/module/senslog1/gateway/SensLog1Gateway.java

+ 5 - 1
build.gradle

@@ -15,7 +15,6 @@ dependencies {
 
 // setting for all projects
 allprojects {
-    sourceCompatibility = 1.8
 
     // set that all projects are Java projects
     apply plugin: 'java'
@@ -28,6 +27,11 @@ allprojects {
         mavenCentral()
     }
 
+    java {
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+    }
+
     dependencies {
         compile group: 'cz.senslog', name: 'common', version: '1.0.0'
     }

+ 8 - 8
config/test.yaml

@@ -53,12 +53,12 @@ services:
         path: "<path>"
 
 scheduler:
-  senslog1:
-      period: 30
-      consumer: "schedule-observations"
-      config:
-        startDate: "2020-01-01 12:10:00[Europe/Prague]" # yyyy-MM-DD hh:mm:ss[zone]
-        allowedStations:
-          # unitId: [sensorId...]
+#  senslog1:
+#      period: 30
+#      consumer: "schedule-observations"
+#      config:
+#        startDate: "2020-08-05T12:24:17[Europe/Prague]"
+#        allowedStations:
+#          # unitId: [sensorId...]
 #          10002222: [410010000, 560030000, 340020000, 380010000, 380090000]
-          10002376: [410010000, 560030000, 340020000, 380010000, 380090000]
+##          10002376: [410010000, 560030000, 340020000, 380010000, 380090000]

+ 105 - 10
connector-core/src/main/java/io/connector/core/AbstractGateway.java

@@ -1,7 +1,11 @@
 package io.connector.core;
 
+import io.connector.core.http.RequestUriComponent;
+import io.connector.core.http.RequestUriParser;
 import io.vertx.core.eventbus.DeliveryOptions;
 import io.vertx.core.eventbus.EventBus;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.json.JsonObject;
 import io.vertx.ext.web.Router;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -12,8 +16,9 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 
-import static io.connector.core.AddressPath.*;
 import static io.connector.core.AddressPath.Creator.createNormalized;
+import static io.connector.core.AddressPath.SCHEDULER_CONSUMER;
+import static io.connector.core.AddressPath.SCHEDULER_PROVIDER;
 import static io.connector.core.MessageHeader.*;
 import static java.lang.String.format;
 
@@ -28,12 +33,14 @@ public abstract class AbstractGateway {
     private String moduleId;
     protected String gatewayId;
     private final Set<String> registeredConsumers;
+    private final Set<String> registeredScheduleConsumers;
     private final Map<String, String> schedulerMapping;
 
     protected AbstractGateway(String id, boolean isDefault) {
         this.gatewayId = id;
         this.isDefault = isDefault;
         this.registeredConsumers = new HashSet<>();
+        this.registeredScheduleConsumers = new HashSet<>();
         this.schedulerMapping = new HashMap<>();
     }
 
@@ -73,8 +80,12 @@ public abstract class AbstractGateway {
         registerSchedulerConsumers();
     }
 
+    private boolean isConsumerRegistered(String consumerName) {
+        return registeredConsumers.contains(consumerName) || registeredScheduleConsumers.contains(consumerName);
+    }
+
     private String createAddress(String consumerName) {
-        if (!registeredConsumers.contains(consumerName)) {
+        if (!isConsumerRegistered(consumerName)) {
             throw logger.throwing(new RuntimeException(
                     format("Consumer '%s' in module %s and gateway %s is not registered.",
                             consumerName, moduleId, gatewayId)
@@ -83,8 +94,13 @@ public abstract class AbstractGateway {
         return createNormalized(moduleId, gatewayId, consumerName);
     }
 
+    protected RequestUriComponent parseUriToComponents(HttpServerRequest request) {
+        return RequestUriParser.parse(request, moduleId, gatewayId);
+    }
+
     public interface SchedulerMapping {
         SchedulerMapping addMapping(String from, String to);
+
     }
 
     protected final SchedulerMapping schedulerMapping() {
@@ -114,33 +130,70 @@ public abstract class AbstractGateway {
         }
 
         String baseAddr = isDefault ? SCHEDULER_PROVIDER : SCHEDULER_CONSUMER;
-        eventBus.consumer(createNormalized(baseAddr, gatewayId), message -> {
+        String gatewayAddr = createNormalized(baseAddr, gatewayId);
+        logger.info("[{}/{}] Creating a consumer at the address {}.", moduleId, gatewayId, gatewayAddr);
+
+        eventBus.consumer(gatewayAddr, message -> {
+            logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, message.address());
+            String schedulerConsumerName = message.headers().get(ADDRESS);
+            if (!registeredScheduleConsumers.contains(schedulerConsumerName)) {
+                logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, schedulerConsumerName);
+                message.fail(400, "No registered consumer for the address " + schedulerConsumerName);
+            } else {
+                String schedulerConsumerAddr = createAddress(schedulerConsumerName);
+                logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, schedulerConsumerAddr);
+                eventBus.request(schedulerConsumerAddr, message.body(), schedulerConsumerReply -> {
+                    if (schedulerConsumerReply.succeeded()) {
+                        io.vertx.core.eventbus.Message<Object> schedulerConsumerMsg = schedulerConsumerReply.result();
+                        logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, schedulerConsumerMsg.address());
+                        message.reply(schedulerConsumerMsg.body());
+                    } else {
+                        logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, schedulerConsumerAddr);
+                        message.fail(400, schedulerConsumerReply.cause().getMessage());
+                    }
+                });
+            }
+        });
+
+        /*
+        eventBus.consumer(gatewayAddr, message -> {
+            logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, message.address());
             if (isDefault) {
                 String proxyConsumerName = message.headers().get(ADDRESS);
                 // this is a provider and must request a local consumer
                 DeliveryOptions proxyOptions = new DeliveryOptions().setHeaders(message.headers());
                 // request proxy consumer (eq scheduler-observations)
-                eventBus.request(createAddress(proxyConsumerName), message.body(), proxyOptions, proxyReply -> {
+                String proxyAddr = createAddress(proxyConsumerName);
+                logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, proxyAddr);
+                eventBus.request(proxyAddr, message.body(), proxyOptions, proxyReply -> {
                     if (proxyReply.succeeded()) {
                         io.vertx.core.eventbus.Message<Object> proxyMsg = proxyReply.result();
+                        logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, proxyMsg.address());
                         String providerProxyName = proxyMsg.headers().get(ADDRESS); // hit: consumerName is the same as providerProxyName
                         String providerName = schedulerMapping.get(providerProxyName);
                         if (providerName != null) {
                             DeliveryOptions providerOptions = new DeliveryOptions().setHeaders(proxyMsg.headers());
                             // request provider consumer (eq observations)
-                            eventBus.request(createAddress(providerName), proxyMsg.body(), providerOptions, providerReply -> {
+                            String providerAddr = createAddress(providerName);
+                            logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, providerAddr);
+                            eventBus.request(providerAddr, proxyMsg.body(), providerOptions, providerReply -> {
                                 if (providerReply.succeeded()) {
                                     io.vertx.core.eventbus.Message<Object> providerMsg = providerReply.result();
+                                    logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, providerMsg.address());
                                     DeliveryOptions replyOptions = new DeliveryOptions().setHeaders(providerMsg.headers());
+                                    logger.info("[{}/{}] Reply of the message {}.", moduleId, gatewayId, message.address());
                                     message.reply(providerMsg.body(), replyOptions);
                                 }else {
-                                    message.fail(400, proxyReply.cause().getMessage());
+                                    logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
+                                    message.fail(400, providerReply.cause().getMessage());
                                 }
                             });
                         } else {
+                            logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
                             message.fail(400, "No conversion endpoint for " + providerName);
                         }
                     } else {
+                        logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
                         message.fail(400, proxyReply.cause().getMessage());
                     }
                 });
@@ -149,26 +202,37 @@ public abstract class AbstractGateway {
                 String consumerName = schedulerMapping.get(providerName);
                 if (consumerName != null) {
                     DeliveryOptions consumerOptions = new DeliveryOptions().setHeaders(message.headers());
-                    eventBus.request(createAddress(consumerName), message.body(), consumerOptions, consumerReply -> {
+                    String consumerAddr = createAddress(consumerName);
+                    logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, consumerAddr);
+                    eventBus.request(consumerAddr, message.body(), consumerOptions, consumerReply -> {
                        if (consumerReply.succeeded()) {
                            io.vertx.core.eventbus.Message<Object> consumerMsg = consumerReply.result();
+                           logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, consumerMsg.address());
                            DeliveryOptions replyOptions = new DeliveryOptions().setHeaders(consumerMsg.headers());
+                           logger.info("[{}/{}] Reply of the message {}.", moduleId, gatewayId, message.address());
                            message.reply(consumerMsg.body(), replyOptions);
                        } else {
+                           logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
+                           logger.catching(consumerReply.cause());
                            message.fail(400, consumerReply.cause().getMessage());
                        }
                     });
                 } else {
+                    logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
                     message.fail(400, "No conversion endpoint for " + providerName);
                 }
             }
         });
+
+         */
     }
 
 
     protected interface Event {
+        <T> void consumeScheduler(String address, Consumer<Message<T>> handler);
         <T> void consume(String address, Consumer<Message<T>> handler);
         <T> void send(String address, Object body, DeliveryOptions options, Consumer<Message<T>> handler);
+        <T> void send(String address, Object body, Consumer<Message<T>> handler);
     }
 
     private class EventImpl implements Event {
@@ -180,9 +244,29 @@ public abstract class AbstractGateway {
         }
 
         @Override
+        public <T> void consumeScheduler(String address, Consumer<Message<T>> handler) {
+            if (registeredScheduleConsumers.contains(address)) {
+                throw logger.throwing(new RuntimeException(
+                        format("Consumer '%s' in module %s and gateway %s is already registered as a scheduler consumer.",
+                                address, moduleId, gatewayId
+                        )
+                ));
+            }
+            registeredScheduleConsumers.add(address);
+            String consumerAddr = createAddress(address);
+            logger.info("[{}/{}] Creating a consumer at the address {}.", moduleId, gatewayId, consumerAddr);
+            eventBus.<T>consumer(consumerAddr, message -> {
+                // TODO vymyslet, jak registrovat scheudler tak, aby byl dosazitelny pouze ze scheduleru
+            });
+        }
+
+        @Override
         public <T> void consume(String address, Consumer<Message<T>> handler) {
             registeredConsumers.add(address);
-            eventBus.<T>consumer(createAddress(address), message -> {
+            String consumerAddr = createAddress(address);
+            logger.info("[{}/{}] Creating a consumer at the address {}.", moduleId, gatewayId, consumerAddr);
+            eventBus.<T>consumer(consumerAddr, message -> {
+                logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, message.address());
                 Message<T> msg = new Message<>(message);
                 try {
                     handler.accept(msg);
@@ -191,6 +275,7 @@ public abstract class AbstractGateway {
                 }
                 if (msg.isFail()) {
                     Fail fail = msg.cause();
+                    logger.error("[{}/{}] Creating a fail message for the address {}.", moduleId, gatewayId, message.address());
                     message.fail(fail.getCode(), fail.getMessage());
                 } else {
                     Reply<Object> reply = msg.reply;
@@ -199,9 +284,11 @@ public abstract class AbstractGateway {
                                 .addHeader(MODULE_TYPE, moduleId)
                                 .addHeader(GATEWAY_TYPE, gatewayId)
                                 .addHeader(ADDRESS, address);
+                        logger.info("[{}/{}] Reply of the message {}.", moduleId, gatewayId, message.address());
                         message.reply(reply.data, reply.options());
                     } else {
-                        message.fail(204, "no content");
+                        logger.info("[{}/{}] Reply empty message for the address {}.", moduleId, gatewayId, message.address());
+                        message.reply(new JsonObject());
                     }
                 }
             });
@@ -209,9 +296,12 @@ public abstract class AbstractGateway {
 
         @Override
         public <T> void send(String address, Object body, DeliveryOptions options, Consumer<Message<T>> handler) {
-            eventBus.<T>request(createAddress(address), body, options, reply -> {
+            String requestAddr = createAddress(address);
+            logger.info("[{}/{}] Creating a request at the address {}.", moduleId, gatewayId, requestAddr);
+            eventBus.<T>request(requestAddr, body, options, reply -> {
                 Message<T> message;
                 if (reply.succeeded()) {
+                    logger.info("[{}/{}] Handling request from address {}.", moduleId, gatewayId, reply.result().address());
                     message = new Message<>(reply.result());
                 } else {
                     message = new Message<>(reply.cause());
@@ -219,5 +309,10 @@ public abstract class AbstractGateway {
                 handler.accept(message);
             });
         }
+
+        @Override
+        public <T> void send(String address, Object body, Consumer<Message<T>> handler) {
+            send(address, body, new DeliveryOptions(), handler);
+        }
     }
 }

+ 10 - 0
connector-core/src/main/java/io/connector/core/Handler.java

@@ -21,4 +21,14 @@ public final class Handler {
             }
         };
     }
+
+    public static <T> Consumer<Message<T>> replyToEventContext(Message<T> message) {
+        return reply -> {
+            if (reply.success()) {
+                message.reply(reply.body());
+            } else {
+                message.fail(reply.cause().getCode(), reply.cause().getMessage());
+            }
+        };
+    }
 }

+ 4 - 1
connector-core/src/main/java/io/connector/core/ModuleDeployer.java

@@ -33,7 +33,10 @@ public class ModuleDeployer extends AbstractVerticle {
 
         CompositeFuture.all(futureModules).onComplete(result -> {
             if(result.succeeded()) {
-                deployHelper(vertx, new DeploymentOptions().setConfig(config()), new VertxServer(modules))
+                DeploymentOptions options = new DeploymentOptions()
+                        .setWorker(true)
+                        .setConfig(config());
+                deployHelper(vertx, options, new VertxServer(modules))
                         .onSuccess(startPromise::complete).onFailure(startPromise::fail);
             } else {
                 startPromise.fail(result.cause());

+ 1 - 1
connector-core/src/main/java/io/connector/core/ModuleInfo.java

@@ -29,7 +29,7 @@ public class ModuleInfo {
     }
 
     public static MessageCodec<ModuleInfo, ModuleInfo> createCodec() {
-        return new MessageCodec<>() {
+        return new MessageCodec<ModuleInfo, ModuleInfo>() {
             @Override
             public void encodeToWire(Buffer buffer, ModuleInfo moduleInfo) {
                 JsonObject jsonToEncode = new JsonObject();

+ 11 - 1
connector-core/src/main/java/io/connector/core/config/PropertyConfig.java

@@ -7,9 +7,11 @@ import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
 import java.util.*;
 
 import static java.lang.String.format;
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
 import static java.util.Collections.emptySet;
 import static java.util.Optional.ofNullable;
 
@@ -122,7 +124,15 @@ public class PropertyConfig {
         if (object instanceof ZonedDateTime) {
             return (ZonedDateTime)object;
         } else if (object instanceof String) {
-            return ZonedDateTime.parse((String)object, DateTimeFormatter.ISO_ZONED_DATE_TIME);
+            final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
+                    .append(ISO_LOCAL_DATE_TIME)
+                    .optionalStart()
+                    .appendLiteral('[')
+                    .parseCaseSensitive()
+                    .appendZoneRegionId()
+                    .appendLiteral(']')
+                    .toFormatter();
+            return ZonedDateTime.parse((String)object, formatter);
         } else {
             throw new ClassCastException(format(
                     "Property '%s' can not be cast to %s", getNewPropertyId(name), ZonedDateTime.class)

+ 48 - 0
connector-core/src/main/java/io/connector/core/http/RequestUriComponent.java

@@ -0,0 +1,48 @@
+package io.connector.core.http;
+
+import static io.connector.core.AddressPath.Creator.create;
+
+public class RequestUriComponent {
+
+    private final String domain;
+    private final String prefix;
+    private final String module;
+    private final String gateway;
+    private final String address;
+
+    RequestUriComponent(String domain, String prefix, String module, String gateway, String address) {
+        this.domain = domain;
+        this.prefix = prefix;
+        this.module = module;
+        this.gateway = gateway;
+        this.address = address;
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public String getGateway() {
+        return gateway;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public String getModuleUri() {
+        return domain + create(prefix, module);
+    }
+
+    public String getGatewayUri() {
+        return domain + create(prefix, module, gateway);
+    }
+}

+ 15 - 0
connector-core/src/main/java/io/connector/core/http/RequestUriParser.java

@@ -0,0 +1,15 @@
+package io.connector.core.http;
+
+import io.vertx.core.http.HttpServerRequest;
+
+import static io.connector.core.AddressPath.Creator.create;
+
+public final class RequestUriParser {
+
+    public static RequestUriComponent parse(HttpServerRequest req, String moduleId, String gatewayId) {
+        String domain = req.absoluteURI().replace(req.path(), "");
+        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);
+    }
+}

+ 2 - 1
connector-model/src/main/java/io/connector/model/senslog1/Observation.java

@@ -29,6 +29,7 @@ public class Observation {
         }
 
         OffsetDateTime timestamp;
+        // TODO create a specific DateTimeFormatterBuilder to parse it
         if (jsonObject.containsKey("time")) {
             final DateTimeFormatter formatter = ofPattern("yyyy-MM-dd HH:mm:ssZ");
             String timeString = jsonObject.getString("time");
@@ -62,7 +63,7 @@ public class Observation {
     }
 
     public static MessageCodec<Observation, Observation> createCodec() {
-        return new MessageCodec<>() {
+        return new MessageCodec<Observation, Observation>() {
 
             @Override
             public void encodeToWire(Buffer buffer, Observation entries) {

+ 1 - 1
connector-model/src/main/java/io/connector/model/senslog1/SensorData.java

@@ -62,7 +62,7 @@ public class SensorData {
     }
 
     public static MessageCodec<SensorData, SensorData> createCodec() {
-        return new MessageCodec<>() {
+        return new MessageCodec<SensorData, SensorData>() {
 
             @Override
             public void encodeToWire(Buffer buffer, SensorData sensor) {

+ 1 - 1
connector-model/src/main/java/io/connector/model/senslog1/UnitData.java

@@ -113,7 +113,7 @@ public class UnitData {
     }
 
     public static MessageCodec<UnitData, UnitData> createCodec() {
-        return new MessageCodec<>() {
+        return new MessageCodec<UnitData, UnitData>() {
 
             @Override
             public void encodeToWire(Buffer buffer, UnitData unit) {

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

@@ -0,0 +1,174 @@
+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;
+
+public class Datastream extends JsonObject {
+
+    public void setId(Integer id) {
+        put("@iot.id", id);
+    }
+
+    public Integer getId() {
+        return getInteger("@iot.id");
+    }
+
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
+    }
+
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
+    }
+
+    public void setThingNavigationLink(String thingsNavigationLink) {
+        put("Thing@iot.navigationLink", thingsNavigationLink);
+    }
+
+    public String getThingNavigationLink() {
+        return getString("Thing@iot.navigationLink");
+    }
+
+    public void setSensorNavigationLink(String sensorNavigationLink) {
+        put("Sensor@iot.navigationLink", sensorNavigationLink);
+    }
+
+    public String getSensorNavigationLink() {
+        return getString("Sensor@iot.navigationLink");
+    }
+
+    public void setObservedPropertyNavigationLink(String observedPropertyNavigationLink) {
+        put("ObservedProperty@iot.navigationLink", observedPropertyNavigationLink);
+    }
+
+    public String getObservedPropertyNavigationLink() {
+        return getString("ObservedProperty@iot.navigationLink");
+    }
+
+    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 setUnitOfMeasurement(Unit unitOfMeasurement) {
+        put("unitOfMeasurement", unitOfMeasurement);
+    }
+
+    public Unit getUnitOfMeasurement() {
+        return (Unit)getJsonObject("unitOfMeasurement");
+    }
+
+    public void setObservationType(String observationType) {
+        put("observationType", observationType);
+    }
+
+    public String getObservationType() {
+        return getString("observationType");
+    }
+
+    public void setObservedArea(Area observedArea) {
+        put("observedArea", observedArea);
+    }
+
+    public Area getObservedArea() {
+        return (Area)getJsonObject("observedArea");
+    }
+
+    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 static class Area extends JsonObject {
+
+        public void setType(String type) {
+            put("type", type);
+        }
+
+        public String getType() {
+            return getString("type");
+        }
+
+        public void setCoordinates(List<List<Integer>> coordinates) {
+            JsonArray jsonCoordinates = new JsonArray();
+            for (List<Integer> coordinate : coordinates) {
+                jsonCoordinates.add(new JsonArray(coordinate));
+            }
+            put("coordinates", jsonCoordinates);
+        }
+
+        public List<List<Integer>> getCoordinates() {
+            JsonArray jsonCoordinates = getJsonArray("coordinates");
+            List<List<Integer>> coordinates = new ArrayList<>(jsonCoordinates.size());
+            for (int c = 0; c < jsonCoordinates.size(); c++) {
+                JsonArray jsonCoordinate = jsonCoordinates.getJsonArray(c);
+                List<Integer> coordinate = new ArrayList<>(jsonCoordinate.size());
+                for (int i = 0; i < jsonCoordinate.size(); i++) {
+                    coordinate.add(jsonCoordinate.getInteger(i));
+                }
+                coordinates.add(coordinate);
+            }
+            return coordinates;
+        }
+    }
+
+    public static class Unit 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");
+        }
+    }
+}

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

@@ -0,0 +1,81 @@
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+public class FeatureOfInterest extends JsonObject {
+
+    public void setId(Integer id) {
+        put("@iot.id", id);
+    }
+
+    public Integer getId() {
+        return getInteger("@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");
+        }
+    }
+}

+ 25 - 0
connector-model/src/main/java/io/connector/model/sensorthings/Geometry.java

@@ -0,0 +1,25 @@
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+
+import java.util.List;
+
+public class Geometry extends JsonObject {
+
+    public void setType(String type) {
+        put("type", type);
+    }
+
+    public String getType() {
+        return getString("type");
+    }
+
+    public void setCoordinates(List<Double> coordinates) {
+        put("coordinates", new JsonArray(coordinates));
+    }
+
+    public List<Double> getCoordinates() {
+        return (List<Double>) getJsonArray("coordinates").getList();
+    }
+}

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

@@ -0,0 +1,74 @@
+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;
+
+public class HistoricalLocation extends JsonObject {
+
+    public void setValue(List<Value> values) {
+        put("value", new JsonArray(values));
+    }
+
+    public List<Value> getValue() {
+        JsonArray jsonValue = getJsonArray("value");
+        List<Value> value = new ArrayList<>(jsonValue.size());
+        for (int i = 0; i < jsonValue.size(); i++) {
+            value.add((Value)jsonValue.getJsonObject(i));
+        }
+        return value;
+    }
+
+    public void setNextLink(String nextLink) {
+        put("@iot.nextLink", nextLink);
+    }
+
+    public String getNextLink() {
+        return getString("@iot.nextLink");
+    }
+
+    public static class Value extends JsonObject {
+
+        public void setId(Integer id) {
+            put("@iot.id", id);
+        }
+
+        public Integer getId() {
+            return getInteger("@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");
+        }
+    }
+}

+ 89 - 0
connector-model/src/main/java/io/connector/model/sensorthings/Location.java

@@ -0,0 +1,89 @@
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+public class Location extends JsonObject {
+
+    public void setId(Integer id) {
+        put("@iot.id", id);
+    }
+
+    public Integer getId() {
+        return getInteger("@iot.id");
+    }
+
+    public void setSelfLink(String selfLink) {
+        put("@iot.selfLink", selfLink);
+    }
+
+    public String getSelfLink() {
+        return getString("@iot.selfLink");
+    }
+
+    public void setThingsNavigationLink(String thingsNavigationLink) {
+        put("Things@iot.navigationLink", thingsNavigationLink);
+    }
+
+    public String getThingsNavigationLink() {
+        return getString("Things@iot.navigationLink");
+    }
+
+    public void setHistoricalLocationsNavigationLink(String historicalLocationsNavigationLink) {
+        put("HistoricalLocations@iot.navigationLink", historicalLocationsNavigationLink);
+    }
+
+    public String getHistoricalLocationsNavigationLink() {
+        return getString("HistoricalLocations@iot.navigationLink");
+    }
+
+    public void setEncodingType(String encodingType) {
+        put("encodingType", encodingType);
+    }
+
+    public String getEncodingType() {
+        return getString("encodingType");
+    }
+
+    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 setLocation(Info 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");
+        }
+    }
+}

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

@@ -0,0 +1,62 @@
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+public class Observation extends JsonObject {
+
+    public void setId(Integer id) {
+        put("@iot.id", id);
+    }
+
+    public Integer getId() {
+        return getInteger("@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");
+    }
+}

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

@@ -0,0 +1,54 @@
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+public class ObservedProperty extends JsonObject {
+
+    public void setId(Integer id) {
+        put("@iot.id", id);
+    }
+
+    public Integer getId() {
+        return getInteger("@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 setDefinition(String definition) {
+        put("definition", definition);
+    }
+
+    public String getDefinition() {
+        return getString("definition");
+    }
+}

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

@@ -0,0 +1,62 @@
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+public class Sensor extends JsonObject {
+
+    public void setId(Integer id) {
+        put("@iot.id", id);
+    }
+
+    public Integer getId() {
+        return getInteger("@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");
+    }
+}

+ 72 - 0
connector-model/src/main/java/io/connector/model/sensorthings/Think.java

@@ -0,0 +1,72 @@
+package io.connector.model.sensorthings;
+
+import io.vertx.core.json.JsonObject;
+
+
+public class Think extends JsonObject {
+
+    public void setId(Integer id) {
+        put("@iot.id", id);
+    }
+
+    public Integer getId() {
+        return getInteger("@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");
+    }
+}

+ 1 - 2
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCClient.java

@@ -1,14 +1,13 @@
 package io.connector.module.afarcloud;
 
+import cz.senslog.common.http.HttpClient;
 import io.connector.core.config.HostConfig;
 import io.connector.model.afarcloud.MultiSimpleObservation;
 import io.connector.module.afarcloud.gateway.SensLog1Gateway;
-import io.vertx.core.json.Json;
 import io.vertx.core.json.JsonObject;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.net.http.HttpClient;
 import java.util.List;
 
 public class AFCClient {

+ 3 - 1
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCModule.java

@@ -1,9 +1,10 @@
 package io.connector.module.afarcloud;
 
-import io.connector.core.ModuleDescriptor;
 import io.connector.core.AbstractModule;
+import io.connector.core.ModuleDescriptor;
 import io.connector.core.ModuleInfo;
 import io.connector.module.afarcloud.gateway.AFCGateway;
+import io.connector.module.afarcloud.gateway.OGCSensorThingsGateway;
 import io.connector.module.afarcloud.gateway.SensLog1Gateway;
 
 
@@ -20,6 +21,7 @@ public class AFCModule extends AbstractModule {
     public void run() {
         registerGateway(new AFCGateway("AFarCloud", client));
         registerGateway(new SensLog1Gateway("SensLogV1", client));
+        registerGateway(new OGCSensorThingsGateway("OGCSensorThings"));
     }
 
     @Override

+ 2 - 3
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/AFCModuleProvider.java

@@ -1,11 +1,10 @@
 package io.connector.module.afarcloud;
 
-import io.connector.core.ModuleDescriptor;
+import cz.senslog.common.http.HttpClient;
 import io.connector.core.AbstractModule;
+import io.connector.core.ModuleDescriptor;
 import io.connector.core.ModuleProvider;
 
-import java.net.http.HttpClient;
-
 public final class AFCModuleProvider implements ModuleProvider {
 
     @Override

+ 203 - 0
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/OGCSensorThingsGateway.java

@@ -0,0 +1,203 @@
+package io.connector.module.afarcloud.gateway;
+
+import io.connector.core.AbstractGateway;
+import io.connector.core.http.RequestUriComponent;
+import io.connector.model.sensorthings.*;
+import io.vertx.core.json.JsonObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static cz.senslog.common.http.HttpContentType.APPLICATION_JSON;
+import static cz.senslog.common.http.HttpHeader.CONTENT_TYPE;
+import static io.connector.core.AddressPath.Creator.create;
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+
+public class OGCSensorThingsGateway extends AbstractGateway {
+
+    public OGCSensorThingsGateway(String id) {
+        super(id);
+    }
+
+    @Override
+    protected void run() {
+
+        router().get(create("Things(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            Think think = new Think();
+            think.setId(id);
+            think.setSelfLink(format("%s/Things(%s)", uriComponent.getGatewayUri(), id));
+            think.setLocationsNavigationLink(format("Things(%s)/Locations", id));
+            think.setDataStreamNavigationLink(format("Things(%s)/Datastreams", id));
+            think.setHistoricalLocationsNavigationLink(format("Things(%s)/HistoricalLocations", id));
+            think.setName("Oven");
+            think.setDescription("This thing is an oven.");
+            think.setProperties(new JsonObject()
+                    .put("owner", "Noah Liang")
+                    .put("color", "Black")
+            );
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(think.encode());
+        });
+
+        router().get(create("Locations(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            Location location = new Location();
+            location.setId(id);
+            location.setSelfLink(format("%s/Locations(%s)", uriComponent.getGatewayUri(), id));
+            location.setHistoricalLocationsNavigationLink(format("Locations(%s)/HistoricalLocations", id));
+            location.setEncodingType("application/vnd.geo+json");
+
+            location.setName("CCIT");
+            location.setDescription("Calgary Center for Innvative Technologies");
+
+            Location.Info info = new Location.Info();
+            info.setType("Feature");
+            location.setLocation(info);
+
+            Geometry geometry = new Geometry();
+            geometry.setType("Point");
+            geometry.setCoordinates(asList(-114.06,51.05));
+            info.setGeometry(geometry);
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(location.encode());
+        });
+
+        router().get(create("Datastreams(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            Datastream datastream = new Datastream();
+            datastream.setId(id);
+            datastream.setSelfLink(format("%s/Datastreams(%s)", uriComponent.getGatewayUri(), id));
+            datastream.setThingNavigationLink(format("HistoricalLocations(%s)/Thing", id));
+            datastream.setSensorNavigationLink(format("Datastreams(%s)/Sensor", id));
+            datastream.setObservedPropertyNavigationLink(format("Datastreams(%s)/ObservedProperty", id));
+            datastream.setObservationsNavigationLink(format("Datastreams(%s)/Observations", id));
+            datastream.setName("oven temperature");
+            datastream.setDescription("This is a datastream measuring the air temperature in an oven.");
+
+            Datastream.Unit unit = new Datastream.Unit();
+            unit.setName("degree Celsius");
+            unit.setSymbol("\u00B0" + "C");
+            unit.setDefinition("http://unitsofmeasure.org/ucum.html#para-30");
+            datastream.setUnitOfMeasurement(unit);
+
+            datastream.setObservationType("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement");
+
+            Datastream.Area area = new Datastream.Area();
+            area.setType("Polygon");
+            area.setCoordinates(asList(
+                    asList(100, 0),
+                    asList(101, 0),
+                    asList(101, 1),
+                    asList(100, 1),
+                    asList(100, 0)
+            ));
+            datastream.setObservedArea(area);
+
+            datastream.setPhenomenonTime("2014-03-01T13:00:00Z/2015-05-11T15:30:00Z");
+            datastream.setResultTime("2014-03-01T13:00:00Z/2015-05-11T15:30:00Z");
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(datastream.encode());
+        });
+
+        router().get(create("Things(:id)/HistoricalLocations")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            HistoricalLocation historicalLocation = new HistoricalLocation();
+
+            List<HistoricalLocation.Value> valueList = new ArrayList<>();
+            for (int valueId = id; valueId < id + 2; valueId++) {
+                HistoricalLocation.Value value = new HistoricalLocation.Value();
+                value.setId(valueId);
+                value.setSelfLink(format("%s/HistoricalLocations(%s)", uriComponent.getGatewayUri(), valueId));
+                value.setLocationsNavigationLink(format("HistoricalLocations(%s)/Locations", valueId));
+                value.setThingNavigationLink(format("HistoricalLocations(%s)/Thing", valueId));
+                value.setTime("2015-01-25T12:00:00-07:00");
+                valueList.add(value);
+            }
+            historicalLocation.setValue(valueList);
+            historicalLocation.setNextLink(format("%s/Things(%s)/HistoricalLocations?$skip=%s&top=%s", uriComponent.getDomain(), id, valueList.size(), valueList.size()));
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(historicalLocation.encode());
+        });
+
+        router().get(create("Sensors(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            Sensor sensor = new Sensor();
+            sensor.setId(id);
+            sensor.setSelfLink(format("%s/Sensors(%s)", uriComponent.getGatewayUri(), id));
+            sensor.setDataStreamNavigationLink(format("Sensors(%s)/Datastreams", id));
+            sensor.setName("TMP36");
+            sensor.setDescription("TMP36 - Analog Temperature sensor");
+            sensor.setEncodingType("application/pdf");
+            sensor.setMetadata("http://example.org/TMP35_36_37.pdf");
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(sensor.encode());
+        });
+
+        router().get(create("ObservedProperties(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            ObservedProperty observedProperty = new ObservedProperty();
+            observedProperty.setId(id);
+            observedProperty.setSelfLink(format("%s/ObservedProperties(%s)", uriComponent.getGatewayUri(), id));
+            observedProperty.setDataStreamNavigationLink(format("ObservedProperties(%s)/Datastreams", id));
+            observedProperty.setDescription("The dewpoint temperature is the temperature to which the air must be cooled, at constant pressure, for dew to form. As the grass and other objects near the ground cool to the dewpoint, some of the water vapor in the atmosphere condenses into liquid water on the objects.");
+            observedProperty.setName("DewPoint Temperature");
+            observedProperty.setDefinition("http://dbpedia.org/page/Dew_point");
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(observedProperty.encode());
+        });
+
+        router().get(create("Observations(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            Observation observation = new Observation();
+            observation.setId(id);
+            observation.setSelfLink(format("%s/Observations(%s)", uriComponent.getGatewayUri(), id));
+            observation.setFeatureOfInterestNavigationLink(format("Observations(%s)/FeatureOfInterest", id));
+            observation.setDataStreamNavigationLink(format("Observations(%s)/Datastream", id));
+            observation.setPhenomenonTime("2014-12-31T11:59:59.00+08:00");
+            observation.setResultTime("2014-12-31T11:59:59.00+08:00");
+            observation.setResult(70.4);
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(observation.encode());
+        });
+
+        router().get(create("FeaturesOfInterest(:id)")).handler(ctx -> {
+            RequestUriComponent uriComponent = parseUriToComponents(ctx.request());
+            int id = Integer.parseInt(ctx.pathParam("id"));
+
+            FeatureOfInterest featureOfInterest = new FeatureOfInterest();
+            featureOfInterest.setId(id);
+            featureOfInterest.setSelfLink(format("%s/FeaturesOfInterest(%s)", uriComponent.getGatewayUri(), id));
+            featureOfInterest.setObservationsNavigationLink(format("FeaturesOfInterest(%s)/Observations", id));
+            featureOfInterest.setName("Weather Station YYC.");
+            featureOfInterest.setDescription("This is a weather station located at the Calgary Airport.");
+            featureOfInterest.setEncodingType("application/vnd.geo+json");
+
+            FeatureOfInterest.Feature feature = new FeatureOfInterest.Feature();
+            feature.setType("Feature");
+            featureOfInterest.setFeature(feature);
+
+            Geometry geometry = new Geometry();
+            geometry.setType("Point");
+            geometry.setCoordinates(asList(-114.06, 51.05));
+            feature.setGeometry(geometry);
+
+            ctx.response().putHeader(CONTENT_TYPE, APPLICATION_JSON).end(featureOfInterest.encode());
+        });
+    }
+}

+ 11 - 4
connector-module-afarcloud/src/main/java/io/connector/module/afarcloud/gateway/SensLog1Gateway.java

@@ -4,7 +4,9 @@ import io.connector.core.AbstractGateway;
 import io.connector.core.DataCollection;
 import io.connector.model.afarcloud.MultiSimpleObservation;
 import io.connector.model.afarcloud.SimpleObservation;
-import io.connector.model.senslog1.*;
+import io.connector.model.senslog1.Observation;
+import io.connector.model.senslog1.Sensor;
+import io.connector.model.senslog1.Unit;
 import io.connector.module.afarcloud.AFCClient;
 import io.vertx.core.json.JsonObject;
 import org.apache.logging.log4j.LogManager;
@@ -68,10 +70,16 @@ public class SensLog1Gateway extends AbstractGateway {
 
     private static class Converter {
 
+        static class SensorMapping {
+            // TODO
+        }
+
+        static class UnitMapping {
+            // TODO
+        }
+
         static MultiSimpleObservation convertObservation(Unit sensLogUnit) {
             MultiSimpleObservation afcResult = new MultiSimpleObservation();
-
-//            String resourceId = format("urn:afc:AS07:environmental:UWB:agronode:%s", unitObservations.getId());
             String resourceId = Long.toString(sensLogUnit.getInfo().getId());
             afcResult.setResourceId(resourceId);
             afcResult.setObservations(new ArrayList<>());
@@ -88,6 +96,5 @@ public class SensLog1Gateway extends AbstractGateway {
             }
             return afcResult;
         }
-
     }
 }

+ 5 - 0
connector-module-senslog1/src/main/java/io/connector/module/senslog1/SensLog1HttpClient.java

@@ -61,6 +61,8 @@ public class SensLog1HttpClient {
     public List<UnitData> lastObservations() {
 
         HostConfig host = config.getSensorServiceHost();
+        logger.info("Getting observations from {}.", host.getDomain());
+
         HttpRequest request = HttpRequest.newBuilder().GET()
                 .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
                         .addParam("Operation", "GetLastObservations")
@@ -68,7 +70,10 @@ public class SensLog1HttpClient {
                         .addParam("group", config.getGroup())
                         .build())
                 .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.isOk()) {
             JsonArray jsonArray = new JsonArray(response.getBody());

+ 1 - 2
connector-module-senslog1/src/main/java/io/connector/module/senslog1/SensLog1Module.java

@@ -25,8 +25,7 @@ public class SensLog1Module extends AbstractModule {
         registerGateway(new AFCGateway("AFarCloud", httpClient));
         registerGateway(new OGCSensorThingsGateway("OGCSensorThings", sqlClient));
 
-        SensLog1SchedulerConfig schedulerConfig = new SensLog1SchedulerConfig(descriptor.getSchedulerConfig());
-        registerGateway(new SensLog1Gateway("SensLogV1", schedulerConfig, httpClient));
+        registerGateway(new SensLog1Gateway("SensLogV1", httpClient));
     }
 
     @Override

+ 16 - 44
connector-module-senslog1/src/main/java/io/connector/module/senslog1/gateway/SensLog1Gateway.java

@@ -1,10 +1,7 @@
 package io.connector.module.senslog1.gateway;
 
 import cz.senslog.common.util.Tuple;
-import io.connector.core.AbstractGateway;
-import io.connector.core.DataCollection;
-import io.connector.core.Message;
-import io.connector.core.MessageHeader;
+import io.connector.core.*;
 import io.connector.core.config.AllowedStation;
 import io.connector.model.senslog1.*;
 import io.connector.module.senslog1.SensLog1HttpClient;
@@ -17,6 +14,7 @@ import io.vertx.core.json.JsonObject;
 import java.time.OffsetDateTime;
 import java.util.*;
 
+import static io.connector.core.Handler.replyToEventContext;
 import static java.time.OffsetDateTime.MAX;
 import static java.time.OffsetDateTime.MIN;
 import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
@@ -24,22 +22,22 @@ import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 public class SensLog1Gateway extends AbstractGateway {
 
     private final SensLog1HttpClient client;
-    private final SensLog1SchedulerConfig schedulerConfig;
+    private final SensLog1SchedulerConfig schedulerConfig; // TODO delete in future
 
-    public SensLog1Gateway(String id, SensLog1SchedulerConfig schedulerConfig, SensLog1HttpClient client) {
+    public SensLog1Gateway(String id, SensLog1HttpClient client) {
         super(id, true);
         this.client = client;
-        this.schedulerConfig = schedulerConfig;
+        this.schedulerConfig = null;
     }
 
     @Override
     public void run() {
 
         // TODO rename to a general name
-        schedulerMapping()
-                .addMapping("schedule-observations", "observations-with-info");
+//        schedulerMapping()
+//                .addMapping("schedule-observations", "observations-with-info");
 
-        event().consume("schedule-observations", message -> {
+        event().consumeScheduler("schedule-observations", message -> {
             List<UnitData> unitData = client.lastObservations();
 
             AllowedStation allowedStation = schedulerConfig.getAllowedStations();
@@ -47,7 +45,6 @@ public class SensLog1Gateway extends AbstractGateway {
             for (UnitData unit : unitData) {
                 if (allowedStation.isAllowed(Long.toString(unit.getId()))) {
                     OffsetDateTime from = MAX, to = MIN;
-                    JsonArray allowedSensors = new JsonArray();
                     for (SensorData sensor : unit.getSensors()) {
                         for (Observation observation : sensor.getObservations()) {
                             OffsetDateTime time = observation.getTimestamp();
@@ -57,23 +54,21 @@ public class SensLog1Gateway extends AbstractGateway {
                             if (time.isAfter(to)) {
                                 to = observation.getTimestamp();
                             }
-                            allowedSensors.add(new JsonObject()
-                                    .put("id", sensor.getId())
-                                    .put("timestamp", time.format(ISO_OFFSET_DATE_TIME))
-                            );
                         }
                     }
                     stations.add(new JsonObject()
                             .put("id", unit.getId())
                             .put("fromDate", from.format(ISO_OFFSET_DATE_TIME))
-                            .put("allowedSensors", allowedSensors));
+                    );
                 }
             }
             JsonObject configBody = new JsonObject()
                     .put("startDate", schedulerConfig.getStartDate().format(ISO_OFFSET_DATE_TIME))
                     .put("allowedStations", stations);
 
-            message.reply(configBody);
+            event().send("observations-with-info", configBody, replyToEventContext(message));
+
+//            message.reply(configBody);
         });
 
         event().consume("units", message -> {
@@ -162,9 +157,10 @@ public class SensLog1Gateway extends AbstractGateway {
                     List<Unit> unitData = new ArrayList<>();
                     for (int i = 0; i < allowedStations.size(); i++) {
                         JsonObject station = allowedStations.getJsonObject(i);
-                        String unitId = station.getString("id");
+                        long unitId = station.getLong("id");
                         OffsetDateTime fromDate = OffsetDateTime.parse(station.getString("fromDate"), ISO_OFFSET_DATE_TIME);
-                        unitData.addAll(client.observationsWithInfo(Long.parseLong(unitId), fromDate, fromDate.plusHours(hoursInterval)));
+                        fromDate = fromDate.minusMonths(3);
+                        unitData.addAll(client.observationsWithInfo(unitId, fromDate, fromDate.plusHours(hoursInterval)));
                     }
                     // TODO filter by sensors
                     message.reply(new DataCollection<>(unitData));
@@ -175,33 +171,9 @@ public class SensLog1Gateway extends AbstractGateway {
                     message.fail(400, "Attribute 'fromDate' is required.");
                 }
             } else {
-                message.fail(400, "Attribute 'fromDate' is required.");
+                message.fail(400, "Configuration in body is required.");
             }
         });
-
-        event().consume("observations", message -> {
-            // TODO get filter/config from message.body()
-
-            MultiMap params = message.headers();
-            Tuple<OffsetDateTime, OffsetDateTime> timeRange = getTimeRangeFromParam(message);
-            if (timeRange == null) { return; }
-            OffsetDateTime fromDate = timeRange.getItem1(), toDate = timeRange.getItem2();
-
-            List<UnitData> observations;
-            if (params.contains("unitId")) {
-                long unitId = Long.parseLong(params.get("unitId"));
-                if (params.contains("sensorId")) {
-                    long sensorId = Long.parseLong(params.get("sensorId"));
-                    observations = client.observations(unitId, sensorId, fromDate, toDate);
-                } else {
-                    observations = client.observations(unitId, fromDate, toDate);
-                }
-            } else {
-                observations = client.observations(fromDate, toDate);
-            }
-
-            message.reply(new DataCollection<>(observations));
-        });
     }
 
     private static <T> Tuple<OffsetDateTime, OffsetDateTime> getTimeRangeFromParam(Message<T> message) {