diff --git a/stack-clients/docker-compose.yml b/stack-clients/docker-compose.yml
index 67af642a..08aaf438 100644
--- a/stack-clients/docker-compose.yml
+++ b/stack-clients/docker-compose.yml
@@ -1,6 +1,6 @@
services:
stack-client:
- image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.54.1
+ image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.55.0
secrets:
- blazegraph_password
- postgis_password
diff --git a/stack-clients/pom.xml b/stack-clients/pom.xml
index 1f122256..f04eaa29 100644
--- a/stack-clients/pom.xml
+++ b/stack-clients/pom.xml
@@ -7,7 +7,7 @@
com.cmclinnovations
stack-clients
- 1.54.1
+ 1.55.0
Stack Clients
https://theworldavatar.io
@@ -116,7 +116,7 @@
uk.ac.cam.cares.jps
jps-base-lib
- 1.48.0
+ 1.49.0
diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/timeseries/TimeSeriesRDBClient.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/timeseries/TimeSeriesRDBClient.java
new file mode 100644
index 00000000..f8b862f2
--- /dev/null
+++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/timeseries/TimeSeriesRDBClient.java
@@ -0,0 +1,131 @@
+package com.cmclinnovations.stack.clients.timeseries;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.time.Instant;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.cmclinnovations.stack.clients.core.EndpointNames;
+import com.cmclinnovations.stack.clients.ontop.OntopClient;
+import com.cmclinnovations.stack.clients.utils.LocalTempDir;
+import com.cmclinnovations.stack.services.OntopService;
+import com.cmclinnovations.stack.services.ServiceManager;
+import com.cmclinnovations.stack.services.config.ServiceConfig;
+
+import uk.ac.cam.cares.jps.base.exception.JPSRuntimeException;
+import uk.ac.cam.cares.jps.base.timeseries.TimeSeriesRDBClientOntop;
+
+public class TimeSeriesRDBClient extends TimeSeriesRDBClientOntop {
+
+ private static final String UNIX_TRS = "http://dbpedia.org/resource/Unix_time";
+ private static final String GENERIC_TRS = "http://example.org/TRS_placeholder";
+
+ /**
+ * Logger for error output.
+ */
+ private static final Logger LOGGER = LogManager.getLogger(TimeSeriesRDBClient.class);
+
+ // IRI of temporal reference system, used in ontop mapping to indicate reference
+ // of time, e.g. Unix
+ private final String trsIri;
+
+ // name of ontop container
+ private String ontopName = "ontop-timeseries";
+
+ public TimeSeriesRDBClient(Class timeClass) {
+ this(timeClass, null);
+ }
+
+ public TimeSeriesRDBClient(Class timeClass, String trsIri) {
+ super(timeClass);
+ if (null != trsIri) {
+ this.trsIri = trsIri;
+ LOGGER.info("TRS is set to {}", trsIri);
+ } else {
+ if (Instant.class == timeClass) {
+ this.trsIri = UNIX_TRS;
+ LOGGER.info("Time class is Instant, TRS is set to {}", UNIX_TRS);
+ } else {
+ this.trsIri = GENERIC_TRS;
+ LOGGER.info("Time class is not Instant, TRS is set to {}", GENERIC_TRS);
+ }
+ }
+ }
+
+ public void setOntopName(String ontopName) {
+ this.ontopName = ontopName;
+ }
+
+ @Override
+ public List bulkInitTimeSeriesTable(List> dataIRIs, List>> dataClasses,
+ List tsIRIs, Integer srid, Connection conn) {
+ List result = super.bulkInitTimeSeriesTable(dataIRIs, dataClasses, tsIRIs, srid, conn);
+
+ // spin up a new ontop container if it does not exist
+ configureOntop();
+
+ return result;
+ }
+
+ public void configureOntop() {
+ String stackName = System.getenv("STACK_NAME");
+ if (stackName == null) {
+ LOGGER.warn("STACK_NAME not detected, skipping Ontop intialisation");
+ return;
+ }
+
+ ServiceManager serviceManager = new ServiceManager(false);
+
+ ServiceConfig newOntopServiceConfig = serviceManager.duplicateServiceConfig(EndpointNames.ONTOP, ontopName);
+ newOntopServiceConfig.setEnvironmentVariable(OntopService.ONTOP_DB_NAME, "postgres");
+
+ newOntopServiceConfig.getEndpoints()
+ .replaceAll((endpointName, connection) -> new com.cmclinnovations.stack.services.config.Connection(
+ connection.getUrl(),
+ connection.getUri(),
+ URI.create(connection.getExternalPath().toString()
+ .replace(EndpointNames.ONTOP, ontopName))));
+ serviceManager.initialiseService(stackName, ontopName);
+
+ OntopClient ontopClient = OntopClient.getInstance(ontopName);
+
+ // create temporary file for ontop mapping
+ try (LocalTempDir tempDir = new LocalTempDir()) {
+
+ String obda = prepareMapping();
+
+ Path filePath = tempDir.getPath().resolve("ontop.obda");
+ Files.write(filePath, obda.getBytes());
+
+ // sends obda to container
+ ontopClient.updateOBDA(filePath);
+ } catch (IOException e) {
+ throw new JPSRuntimeException("Failed to write ontop mapping into temporary folder", e);
+ }
+ }
+
+ /**
+ * set TRS in Ontop mapping
+ *
+ * @return
+ */
+ private String prepareMapping() {
+ // read template from resources folder
+ try (InputStream is = TimeSeriesRDBClient.class.getResourceAsStream("timeseries_ontop_template.obda")) {
+ return IOUtils.toString(is, StandardCharsets.UTF_8)
+ .replace("[TRS_REPLACE]", trsIri).replace("[SCHEMA]", getSchema());
+ } catch (IOException e) {
+ throw new JPSRuntimeException("Error while reading timeseries_ontop_template.obda", e);
+ }
+ }
+
+}
diff --git a/stack-clients/src/main/resources/com/cmclinnovations/stack/clients/timeseries/timeseries_ontop_template.obda b/stack-clients/src/main/resources/com/cmclinnovations/stack/clients/timeseries/timeseries_ontop_template.obda
new file mode 100644
index 00000000..06506690
--- /dev/null
+++ b/stack-clients/src/main/resources/com/cmclinnovations/stack/clients/timeseries/timeseries_ontop_template.obda
@@ -0,0 +1,29 @@
+[PrefixDeclaration]
+obda: https://w3id.org/obda/vocabulary#
+geo: http://www.opengis.net/ont/geosparql#
+timeprefix: http://www.w3.org/2006/time#
+timeseries: https://www.theworldavatar.com/kg/ontotimeseries/
+
+[MappingDeclaration] @collection [[
+mappingId time_series_data_[SCHEMA]
+target timeseries:data/[SCHEMA]/{data_iri_index} timeseries:hasObservation timeseries:observation/[SCHEMA]/{id} .
+ timeseries:observation/[SCHEMA]/{id} a timeseries:Observation ;
+ timeseries:observationOf timeseries:data/[SCHEMA]/{data_iri_index} ;
+ timeseries:hasResult timeseries:result/[SCHEMA]/{id} ;
+ timeprefix:hasTime timeseries:time/[SCHEMA]/{id} .
+ timeseries:time/[SCHEMA]/{id} a timeprefix:Instant ;
+ timeprefix:inXSDDateTime {time_as_timestamp} ;
+ timeprefix:inTimePosition timeseries:timePosition/[SCHEMA]/{id}.
+ timeseries:timePosition/[SCHEMA]/{id} a timeprefix:TimePosition ;
+ timeprefix:numericPosition {time_as_number} ;
+ timeprefix:unitType timeprefix:unitSecond ;
+ timeprefix:hasTRS <[TRS_REPLACE]> .
+ timeseries:result/[SCHEMA]/{id} a timeseries:Result ;
+ timeseries:hasValue {"double precision"}, {wkt}^^geo:wktLiteral, {int};
+ timeseries:hasUnit {unit} .
+source SELECT id, time_as_number, time_as_timestamp, data_iri_index, "double precision", ST_AsText("geometry(Point,4326)") AS wkt, int, unit FROM "[SCHEMA]".time_series_data
+
+mappingId equivalent_iri_[SCHEMA]
+target <{data_iri}> obda:isCanonicalIRIOf timeseries:data/[SCHEMA]/{data_iri_index} .
+source SELECT data_iri, data_iri_index from "[SCHEMA]".time_series_data_iri
+]]
\ No newline at end of file
diff --git a/stack-data-uploader/docker-compose.yml b/stack-data-uploader/docker-compose.yml
index e07d9ef3..62d8a49a 100644
--- a/stack-data-uploader/docker-compose.yml
+++ b/stack-data-uploader/docker-compose.yml
@@ -1,6 +1,6 @@
services:
stack-data-uploader:
- image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.54.1
+ image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.55.0
secrets:
- blazegraph_password
- postgis_password
diff --git a/stack-data-uploader/pom.xml b/stack-data-uploader/pom.xml
index 85c6c570..f5fb4bb0 100644
--- a/stack-data-uploader/pom.xml
+++ b/stack-data-uploader/pom.xml
@@ -7,7 +7,7 @@
com.cmclinnovations
stack-data-uploader
- 1.54.1
+ 1.55.0
Stack Data Uploader
https://theworldavatar.io
@@ -38,7 +38,7 @@
com.cmclinnovations
stack-clients
- 1.54.1
+ 1.55.0
diff --git a/stack-manager/docker-compose.yml b/stack-manager/docker-compose.yml
index 6f095bd3..fae293a3 100644
--- a/stack-manager/docker-compose.yml
+++ b/stack-manager/docker-compose.yml
@@ -1,6 +1,6 @@
services:
stack-manager:
- image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.54.1
+ image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.55.0
environment:
EXTERNAL_PORT: "${EXTERNAL_PORT-3838}"
STACK_BASE_DIR: "${STACK_BASE_DIR}"
diff --git a/stack-manager/pom.xml b/stack-manager/pom.xml
index 74ae3bd8..8e89c0ee 100644
--- a/stack-manager/pom.xml
+++ b/stack-manager/pom.xml
@@ -7,7 +7,7 @@
com.cmclinnovations
stack-manager
- 1.54.1
+ 1.55.0
Stack Manager
https://theworldavatar.io
@@ -38,7 +38,7 @@
com.cmclinnovations
stack-clients
- 1.54.1
+ 1.55.0