diff --git a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPostDeleteLocation.java b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPostDeleteLocation.java
new file mode 100644
index 000000000..8af4b60ee
--- /dev/null
+++ b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPostDeleteLocation.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131
+ * Karlsruhe, Germany.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+package de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel;
+
+import de.fraunhofer.iosb.ilt.frostserver.model.core.PkValue;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPostDelete;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection;
+import de.fraunhofer.iosb.ilt.frostserver.util.exception.NoSuchEntityException;
+import org.jooq.TableField;
+import org.jooq.impl.DSL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author hylke
+ */
+class HookPostDeleteLocation implements HookPostDelete {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HookPostDeleteLocation.class.getName());
+
+ @Override
+ public void postDelete(JooqPersistenceManager pm, PkValue entityId) throws NoSuchEntityException {
+ final TableCollection tables = pm.getTableCollection();
+ // Also postDelete all historicalLocations that no longer reference any location
+ TableImpHistLocations thl = tables.getTableForClass(TableImpHistLocations.class);
+ TableImpLocationsHistLocations tlhl = tables.getTableForClass(TableImpLocationsHistLocations.class);
+ int count = pm.getDslContext().delete(thl).where(((TableField) thl.getId()).in(DSL.select(thl.getId()).from(thl).leftJoin(tlhl).on(((TableField) thl.getId()).eq(tlhl.getHistLocationId())).where(tlhl.getLocationId().isNull()))).execute();
+ LOGGER.debug("Deleted {} HistoricalLocations", count);
+ }
+
+}
diff --git a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPostInsertHistLoc.java b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPostInsertHistLoc.java
new file mode 100644
index 000000000..740c35c0f
--- /dev/null
+++ b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPostInsertHistLoc.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131
+ * Karlsruhe, Germany.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+package de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel;
+
+import de.fraunhofer.iosb.ilt.frostserver.model.EntityType;
+import de.fraunhofer.iosb.ilt.frostserver.model.core.Entity;
+import de.fraunhofer.iosb.ilt.frostserver.model.ext.TimeInstant;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.EntityFactories;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPostInsert;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection;
+import de.fraunhofer.iosb.ilt.frostserver.property.EntityPropertyMain;
+import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain.NavigationPropertyEntity;
+import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain.NavigationPropertyEntitySet;
+import de.fraunhofer.iosb.ilt.frostserver.util.exception.IncompleteEntityException;
+import de.fraunhofer.iosb.ilt.frostserver.util.exception.NoSuchEntityException;
+import java.util.Collections;
+import java.util.Map;
+import net.time4j.Moment;
+import org.jooq.DSLContext;
+import org.jooq.Field;
+import org.jooq.Record;
+import org.jooq.TableField;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author hylke
+ */
+class HookPostInsertHistLoc implements HookPostInsert {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HookPostInsertHistLoc.class.getName());
+
+ @Override
+ public boolean postInsertIntoDatabase(JooqPersistenceManager pm, Entity histLoc, Map insertFields) throws NoSuchEntityException, IncompleteEntityException {
+ final EntityFactories ef = pm.getEntityFactories();
+ final TableCollection tc = pm.getTableCollection();
+ final EntityType etHistLoc = histLoc.getEntityType();
+ final NavigationPropertyEntity npThing = (NavigationPropertyEntity) etHistLoc.getNavigationProperty("Thing");
+ final NavigationPropertyEntitySet npLocations = (NavigationPropertyEntitySet) etHistLoc.getNavigationProperty("Locations");
+ final EntityPropertyMain epTime = etHistLoc.getEntityProperty("time");
+ Entity thing = histLoc.getProperty(npThing);
+ Object thingId = thing.getPrimaryKeyValues().get(0);
+ DSLContext dslContext = pm.getDslContext();
+ TableImpHistLocations thl = tc.getTableForClass(TableImpHistLocations.class);
+ final TimeInstant hlTime = histLoc.getProperty(epTime);
+ Moment newTime = hlTime.getDateTime();
+ // https://github.com/opengeospatial/sensorthings/issues/30
+ // Check the time of the latest HistoricalLocation of our thing.
+ // If this time is earlier than our time, set the Locations of our Thing to our Locations.
+ Record lastHistLocation = dslContext.select(Collections.emptyList()).from(thl).where(((TableField) thl.getThingId()).eq(thingId).and(thl.time.gt(newTime))).orderBy(thl.time.desc()).limit(1).fetchOne();
+ if (lastHistLocation == null) {
+ // We are the newest.
+ // Unlink old Locations from Thing.
+ TableImpThingsLocations qtl = tc.getTableForClass(TableImpThingsLocations.class);
+ long count = dslContext.delete(qtl).where(((TableField) qtl.getThingId()).eq(thingId)).execute();
+ LOGGER.debug(EntityFactories.UNLINKED_L_FROM_T, count, thingId);
+ // Link new locations to Thing.
+ for (Entity l : histLoc.getProperty(npLocations)) {
+ if (!l.getPrimaryKeyValues().isFullySet() || !ef.entityExists(pm, l, true)) {
+ throw new NoSuchEntityException("Location with no id.");
+ }
+ Object locationId = l.getPrimaryKeyValues().get(0);
+ dslContext.insertInto(qtl).set((TableField) qtl.getThingId(), thingId).set(qtl.getLocationId(), locationId).execute();
+ LOGGER.debug(EntityFactories.LINKED_L_TO_T, locationId, thingId);
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPrePostInsertUpdateLocation.java b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPrePostInsertUpdateLocation.java
new file mode 100644
index 000000000..67a04c048
--- /dev/null
+++ b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPrePostInsertUpdateLocation.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131
+ * Karlsruhe, Germany.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+package de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel;
+
+import de.fraunhofer.iosb.ilt.configurable.annotations.ConfigurableField;
+import de.fraunhofer.iosb.ilt.configurable.editor.EditorString;
+import de.fraunhofer.iosb.ilt.frostserver.model.EntityChangedMessage;
+import de.fraunhofer.iosb.ilt.frostserver.model.EntityType;
+import de.fraunhofer.iosb.ilt.frostserver.model.ModelRegistry;
+import de.fraunhofer.iosb.ilt.frostserver.model.core.Entity;
+import de.fraunhofer.iosb.ilt.frostserver.model.core.EntitySet;
+import de.fraunhofer.iosb.ilt.frostserver.model.core.PkValue;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.EntityFactories;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPostInsert;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPostUpdate;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPreInsert;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPreUpdate;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.StaTable;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection;
+import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain.NavigationPropertyEntitySet;
+import de.fraunhofer.iosb.ilt.frostserver.service.UpdateMode;
+import de.fraunhofer.iosb.ilt.frostserver.util.exception.IncompleteEntityException;
+import de.fraunhofer.iosb.ilt.frostserver.util.exception.NoSuchEntityException;
+import java.util.Map;
+import net.time4j.Moment;
+import org.jooq.DSLContext;
+import org.jooq.Field;
+import org.jooq.TableField;
+import org.jooq.exception.DataAccessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author hylke
+ */
+public class HookPrePostInsertUpdateLocation implements HookPreInsert, HookPostInsert, HookPreUpdate, HookPostUpdate {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HookPrePostInsertUpdateLocation.class);
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "Things Locations LinkTable")
+ @EditorString.EdOptsString(dflt = "THINGS_LOCATIONS")
+ private String ttlName = "THINGS_LOCATIONS";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "LinkTable ThingId")
+ @EditorString.EdOptsString(dflt = "THING_ID")
+ private String ttlThingIdName = "THING_ID";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "LinkTable LocationId")
+ @EditorString.EdOptsString(dflt = "LOCATION_ID")
+ private String ttlLocationIdName = "LOCATION_ID";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "Locations-HistLocations LinkTable")
+ @EditorString.EdOptsString(dflt = "LOCATIONS_HIST_LOCATIONS")
+ private String tlhlName = "LOCATIONS_HIST_LOCATIONS";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "LinkTable HistLocationId")
+ @EditorString.EdOptsString(dflt = "HIST_LOCATION_ID")
+ private String tlhlHistLocationIdName = "HIST_LOCATION_ID";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "Time Field")
+ @EditorString.EdOptsString(dflt = "TIME")
+ private String thlTimeName = "TIME";
+
+ @Override
+ public boolean preInsertIntoDatabase(Phase fase, JooqPersistenceManager pm, Entity entity, Map insertFields) throws NoSuchEntityException, IncompleteEntityException {
+ unlinkExitingLocationsFromLinkedThings(pm, entity);
+ return true;
+ }
+
+ @Override
+ public boolean postInsertIntoDatabase(JooqPersistenceManager pm, Entity entity, Map insertFields) throws NoSuchEntityException, IncompleteEntityException {
+ PkValue entityId = entity.getPrimaryKeyValues();
+ createHistLocationLinkLocations(pm, entity, entityId);
+ return true;
+ }
+
+ @Override
+ public void preUpdateInDatabase(JooqPersistenceManager pm, Entity entity, PkValue entityId, UpdateMode updateMode) throws NoSuchEntityException, IncompleteEntityException {
+ unlinkExitingLocationsFromLinkedThings(pm, entity);
+ }
+
+ @Override
+ public void postUpdateInDatabase(JooqPersistenceManager pm, Entity entity, PkValue entityId, UpdateMode updateMode) throws NoSuchEntityException, IncompleteEntityException {
+ createHistLocationLinkLocations(pm, entity, entityId);
+ }
+
+ private void unlinkExitingLocationsFromLinkedThings(JooqPersistenceManager pm, Entity entity) {
+ TableCollection tables = pm.getTableCollection();
+ EntityType et = entity.getEntityType();
+ NavigationPropertyEntitySet npLocationThings = et.getNavigationPropertyEntitySet("Things");
+ if (npLocationThings == null) {
+ LOGGER.error("EntityType {} has no navigationPropertySet Things", et);
+ return;
+ }
+ NavigationPropertyEntitySet npLocationHistLocs = et.getNavigationPropertyEntitySet("HistoricalLocations");
+ if (npLocationHistLocs == null) {
+ LOGGER.error("EntityType {} has no navigationPropertySet HistoricalLocations", et);
+ return;
+ }
+ EntitySet things = entity.getProperty(npLocationThings);
+ // Maybe create a Historical Location for things linked to this Location.
+ if (things != null && !things.isEmpty()) {
+ for (Entity thing : things) {
+ Object thingId = thing.getPrimaryKeyValues().get(0);
+ if (thingId != null) {
+ DSLContext dslContext = pm.getDslContext();
+ StaTable> ttl = tables.getTableForName(ttlName);
+ // Unlink old Locations from Thing.
+ long count = dslContext.delete(ttl).where(((TableField) ttl.field(ttlThingIdName)).eq(thingId)).execute();
+ LOGGER.debug(EntityFactories.UNLINKED_L_FROM_T, count, thingId);
+ }
+ }
+ }
+ }
+
+ public void createHistLocationLinkLocations(JooqPersistenceManager pm, Entity entity, PkValue entityId) throws DataAccessException {
+ TableCollection tables = pm.getTableCollection();
+ EntityType et = entity.getEntityType();
+ NavigationPropertyEntitySet npLocationThings = et.getNavigationPropertyEntitySet("Things");
+ if (npLocationThings == null) {
+ LOGGER.error("EntityType {} has no navigationPropertySet Things", et);
+ return;
+ }
+ NavigationPropertyEntitySet npLocationHistLocs = et.getNavigationPropertyEntitySet("HistoricalLocations");
+ if (npLocationHistLocs == null) {
+ LOGGER.error("EntityType {} has no navigationPropertySet HistoricalLocations", et);
+ return;
+ }
+ EntityType etHistLoc = npLocationHistLocs.getEntityType();
+ EntitySet things = entity.getProperty(npLocationThings);
+ DSLContext dslContext = pm.getDslContext();
+ Object locationId = entityId.get(0);
+ // Maybe create a Historical Location for things linked to this Location.
+ if (things != null && !things.isEmpty()) {
+ final ModelRegistry modelRegistry = pm.getCoreSettings().getModelRegistry();
+ for (Entity thing : things) {
+ Object thingId = thing.getPrimaryKeyValues().get(0);
+
+ // Create HistoricalLocation for Thing
+ TableImpHistLocations qhl = tables.getTableForClass(TableImpHistLocations.class);
+ Object histLocationId = dslContext.insertInto(qhl)
+ .set((TableField) qhl.getThingId(), thingId)
+ .set(qhl.time, Moment.nowInSystemTime())
+ .returningResult(qhl.getId())
+ .fetchOne(0);
+ LOGGER.debug(EntityFactories.CREATED_HL, histLocationId);
+
+ // Link Location to HistoricalLocation.
+ TableImpLocationsHistLocations qlhl = tables.getTableForClass(TableImpLocationsHistLocations.class);
+ dslContext.insertInto(qlhl)
+ .set((TableField) qlhl.getHistLocationId(), histLocationId)
+ .set(qlhl.getLocationId(), locationId)
+ .execute();
+ LOGGER.debug(EntityFactories.LINKED_L_TO_HL, locationId, histLocationId);
+
+ // Send a message about the creation of a new HL
+ Entity newHl = pm.get(etHistLoc, PkValue.of(histLocationId));
+ newHl.setQuery(modelRegistry.getMessageQueryGenerator().getQueryFor(newHl.getEntityType()));
+ pm.getEntityChangedMessages().add(
+ new EntityChangedMessage()
+ .setEventType(EntityChangedMessage.Type.CREATE)
+ .setEntity(newHl));
+
+ }
+ }
+ }
+
+}
diff --git a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPrePostInsertUpdateThing.java b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPrePostInsertUpdateThing.java
new file mode 100644
index 000000000..5126010f1
--- /dev/null
+++ b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/HookPrePostInsertUpdateThing.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131
+ * Karlsruhe, Germany.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+package de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel;
+
+import de.fraunhofer.iosb.ilt.configurable.annotations.ConfigurableField;
+import de.fraunhofer.iosb.ilt.configurable.editor.EditorString;
+import de.fraunhofer.iosb.ilt.frostserver.model.EntityChangedMessage;
+import de.fraunhofer.iosb.ilt.frostserver.model.EntityType;
+import de.fraunhofer.iosb.ilt.frostserver.model.ModelRegistry;
+import de.fraunhofer.iosb.ilt.frostserver.model.core.Entity;
+import de.fraunhofer.iosb.ilt.frostserver.model.core.EntitySet;
+import de.fraunhofer.iosb.ilt.frostserver.model.core.PkValue;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.EntityFactories;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPostInsert;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPostUpdate;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPreInsert;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.HookPreUpdate;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.StaMainTable;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.StaTable;
+import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection;
+import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain.NavigationPropertyEntitySet;
+import de.fraunhofer.iosb.ilt.frostserver.service.UpdateMode;
+import de.fraunhofer.iosb.ilt.frostserver.util.exception.IncompleteEntityException;
+import de.fraunhofer.iosb.ilt.frostserver.util.exception.NoSuchEntityException;
+import java.util.Map;
+import net.time4j.Moment;
+import org.jooq.DSLContext;
+import org.jooq.Field;
+import org.jooq.TableField;
+import org.jooq.exception.DataAccessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author hylke
+ */
+public class HookPrePostInsertUpdateThing implements HookPreInsert, HookPostInsert, HookPreUpdate, HookPostUpdate {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(HookPrePostInsertUpdateThing.class);
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "Things Locations LinkTable")
+ @EditorString.EdOptsString(dflt = "THINGS_LOCATIONS")
+ private String ttlName = "THINGS_LOCATIONS";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "LinkTable ThingId")
+ @EditorString.EdOptsString(dflt = "THING_ID")
+ private String ttlThingIdName = "THING_ID";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "LinkTable LocationId")
+ @EditorString.EdOptsString(dflt = "LOCATION_ID")
+ private String ttlLocationIdName = "LOCATION_ID";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "Locations-HistLocations LinkTable")
+ @EditorString.EdOptsString(dflt = "LOCATIONS_HIST_LOCATIONS")
+ private String tlhlName = "LOCATIONS_HIST_LOCATIONS";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "LinkTable HistLocationId")
+ @EditorString.EdOptsString(dflt = "HIST_LOCATION_ID")
+ private String tlhlHistLocationIdName = "HIST_LOCATION_ID";
+
+ @ConfigurableField(editor = EditorString.class,
+ label = "Time Field")
+ @EditorString.EdOptsString(dflt = "TIME")
+ private String thlTimeName = "TIME";
+
+ @Override
+ public boolean preInsertIntoDatabase(Phase fase, JooqPersistenceManager pm, Entity entity, Map insertFields) throws NoSuchEntityException, IncompleteEntityException {
+ return true;
+ }
+
+ @Override
+ public boolean postInsertIntoDatabase(JooqPersistenceManager pm, Entity entity, Map insertFields) throws NoSuchEntityException, IncompleteEntityException {
+ PkValue entityId = entity.getPrimaryKeyValues();
+ createHistLocationLinkLocations(pm, entity, entityId);
+ return true;
+ }
+
+ @Override
+ public void preUpdateInDatabase(JooqPersistenceManager pm, Entity entity, PkValue entityId, UpdateMode updateMode) throws NoSuchEntityException, IncompleteEntityException {
+ TableCollection tables = pm.getTableCollection();
+ EntityType et = entity.getEntityType();
+ NavigationPropertyEntitySet npThingLocations = et.getNavigationPropertyEntitySet("Locations");
+ if (npThingLocations == null) {
+ LOGGER.error("Given Entity of type {} has no navigationPropertySet Locations", et);
+ return;
+ }
+ EntitySet locations = entity.getProperty(npThingLocations);
+ if (locations != null && !locations.isEmpty()) {
+ // New locations to be set, delete old locations.
+ Object thingId = entityId.get(0);
+ DSLContext dslContext = pm.getDslContext();
+ StaTable> ttl = tables.getTableForName(ttlName);
+ // Unlink old Locations from Thing.
+ long count = dslContext.delete(ttl).where(((TableField) ttl.field(ttlThingIdName)).eq(thingId)).execute();
+ LOGGER.debug(EntityFactories.UNLINKED_L_FROM_T, count, thingId);
+ }
+ }
+
+ @Override
+ public void postUpdateInDatabase(JooqPersistenceManager pm, Entity entity, PkValue entityId, UpdateMode updateMode) throws NoSuchEntityException, IncompleteEntityException {
+ createHistLocationLinkLocations(pm, entity, entityId);
+ }
+
+ public void createHistLocationLinkLocations(JooqPersistenceManager pm, Entity entity, PkValue entityId) throws DataAccessException {
+ TableCollection tables = pm.getTableCollection();
+ EntityType et = entity.getEntityType();
+ NavigationPropertyEntitySet npThingLocations = et.getNavigationPropertyEntitySet("Locations");
+ if (npThingLocations == null) {
+ LOGGER.error("EntityType {} has no navigationPropertySet Locations", et);
+ return;
+ }
+ NavigationPropertyEntitySet npThingHistLocs = et.getNavigationPropertyEntitySet("HistoricalLocations");
+ if (npThingHistLocs == null) {
+ LOGGER.error("EntityType {} has no navigationPropertySet HistoricalLocations", et);
+ return;
+ }
+ EntityType etHistLoc = npThingHistLocs.getEntityType();
+ EntitySet locations = entity.getProperty(npThingLocations);
+ DSLContext dslContext = pm.getDslContext();
+ Object thingId = entityId.get(0);
+ // Now link the new locations also to a historicalLocation.
+ if (locations != null && !locations.isEmpty()) {
+ // Insert a new HL into the DB
+ StaMainTable> thl = tables.getTableForType(etHistLoc);
+ Object histLocationId = dslContext.insertInto(thl)
+ .set((TableField) thl.field(thlTimeName), Moment.nowInSystemTime())
+ .set((TableField) thl.field(ttlThingIdName), thingId)
+ .returningResult(thl.getPkFields().get(0))
+ .fetchOne(0);
+ LOGGER.debug(EntityFactories.CREATED_HL, histLocationId);
+
+ // Link the locations to the new HL
+ StaTable> tlhl = tables.getTableForName(tlhlName);
+ for (Entity location : locations) {
+ Object locationId = location.getPrimaryKeyValues().get(0);
+ if (locationId == null) {
+ LOGGER.error("Location with no ID");
+ }
+ dslContext.insertInto(tlhl)
+ .set(((TableField) tlhl.field(tlhlHistLocationIdName)), histLocationId)
+ .set((tlhl.field(ttlLocationIdName)), locationId)
+ .execute();
+ LOGGER.debug(EntityFactories.LINKED_L_TO_HL, locationId, histLocationId);
+ }
+
+ // Send a message about the creation of a new HL
+ Entity newHl = pm.get(etHistLoc, PkValue.of(histLocationId));
+ ModelRegistry modelRegistry = pm.getCoreSettings().getModelRegistry();
+ newHl.setQuery(modelRegistry.getMessageQueryGenerator().getQueryFor(newHl.getEntityType()));
+ pm.getEntityChangedMessages().add(
+ new EntityChangedMessage()
+ .setEventType(EntityChangedMessage.Type.CREATE)
+ .setEntity(newHl));
+ }
+ }
+
+ public void setTtlName(String ttlName) {
+ this.ttlName = ttlName;
+ }
+
+ public void setTtlThingIdName(String ttlThingIdName) {
+ this.ttlThingIdName = ttlThingIdName;
+ }
+
+ public void setTtlLocationIdName(String ttlLocationIdName) {
+ this.ttlLocationIdName = ttlLocationIdName;
+ }
+
+ public void setTlhlName(String tlhlName) {
+ this.tlhlName = tlhlName;
+ }
+
+ public void setTlhlHistLocationIdName(String tlhlHistLocationIdName) {
+ this.tlhlHistLocationIdName = tlhlHistLocationIdName;
+ }
+
+ public void setThlTimeName(String thlTimeName) {
+ this.thlTimeName = thlTimeName;
+ }
+
+}
diff --git a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpHistLocations.java b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpHistLocations.java
index d50796dfc..09a1b00e7 100644
--- a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpHistLocations.java
+++ b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpHistLocations.java
@@ -18,8 +18,6 @@
package de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel;
import de.fraunhofer.iosb.ilt.frostserver.model.EntityType;
-import de.fraunhofer.iosb.ilt.frostserver.model.core.Entity;
-import de.fraunhofer.iosb.ilt.frostserver.model.ext.TimeInstant;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.MomentBinding;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.EntityFactories;
@@ -29,15 +27,10 @@
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.PropertyFieldRegistry;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.validator.SecurityTableWrapper;
-import de.fraunhofer.iosb.ilt.frostserver.service.UpdateMode;
-import de.fraunhofer.iosb.ilt.frostserver.util.exception.IncompleteEntityException;
-import de.fraunhofer.iosb.ilt.frostserver.util.exception.NoSuchEntityException;
import de.fraunhofer.iosb.ilt.frostserver.util.user.PrincipalExtended;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import net.time4j.Moment;
-import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Name;
@@ -46,8 +39,6 @@
import org.jooq.TableField;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
public class TableImpHistLocations extends StaTableAbstract {
@@ -56,7 +47,6 @@ public class TableImpHistLocations extends StaTableAbstract(pluginCoreModel.epTime, table -> table.time));
pfReg.addEntry(pluginCoreModel.npThingHistLoc, TableImpHistLocations::getThingId);
pfReg.addEntry(pluginCoreModel.npLocationsHistLoc, TableImpHistLocations::getId);
- }
-
- @Override
- public boolean insertIntoDatabase(JooqPersistenceManager pm, Entity histLoc, UpdateMode updateMode) throws NoSuchEntityException, IncompleteEntityException {
- super.insertIntoDatabase(pm, histLoc, updateMode);
- EntityFactories entityFactories = pm.getEntityFactories();
- Entity thing = histLoc.getProperty(pluginCoreModel.npThingHistLoc);
- Object thingId = thing.getPrimaryKeyValues().get(0);
- DSLContext dslContext = pm.getDslContext();
- TableImpHistLocations thl = getTables().getTableForClass(TableImpHistLocations.class);
-
- final TimeInstant hlTime = histLoc.getProperty(pluginCoreModel.epTime);
- Moment newTime = hlTime.getDateTime();
- // https://github.com/opengeospatial/sensorthings/issues/30
- // Check the time of the latest HistoricalLocation of our thing.
- // If this time is earlier than our time, set the Locations of our Thing to our Locations.
- Record lastHistLocation = dslContext.select(Collections.emptyList())
- .from(thl)
- .where(
- ((TableField) thl.getThingId()).eq(thingId)
- .and(thl.time.gt(newTime)))
- .orderBy(thl.time.desc())
- .limit(1)
- .fetchOne();
- if (lastHistLocation == null) {
- // We are the newest.
- // Unlink old Locations from Thing.
- TableImpThingsLocations qtl = getTables().getTableForClass(TableImpThingsLocations.class);
- long count = dslContext
- .delete(qtl)
- .where(((TableField) qtl.getThingId()).eq(thingId))
- .execute();
- LOGGER.debug(EntityFactories.UNLINKED_L_FROM_T, count, thingId);
-
- // Link new locations to Thing.
- for (Entity l : histLoc.getProperty(pluginCoreModel.npLocationsHistLoc)) {
- if (!l.getPrimaryKeyValues().isFullySet() || !entityFactories.entityExists(pm, l, true)) {
- throw new NoSuchEntityException("Location with no id.");
- }
- Object locationId = l.getPrimaryKeyValues().get(0);
-
- dslContext.insertInto(qtl)
- .set(((TableField) qtl.getThingId()), thingId)
- .set((qtl.getLocationId()), locationId)
- .execute();
- LOGGER.debug(EntityFactories.LINKED_L_TO_T, locationId, thingId);
- }
- }
-
- return true;
+ registerHookPostInsert(0, new HookPostInsertHistLoc());
}
@Override
diff --git a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpLocations.java b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpLocations.java
index 0e00e249e..20852fcc3 100644
--- a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpLocations.java
+++ b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpLocations.java
@@ -17,12 +17,9 @@
*/
package de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel;
-import de.fraunhofer.iosb.ilt.frostserver.model.EntityChangedMessage;
import de.fraunhofer.iosb.ilt.frostserver.model.EntityType;
import de.fraunhofer.iosb.ilt.frostserver.model.ModelRegistry;
import de.fraunhofer.iosb.ilt.frostserver.model.core.Entity;
-import de.fraunhofer.iosb.ilt.frostserver.model.core.EntitySet;
-import de.fraunhofer.iosb.ilt.frostserver.model.core.PkValue;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.JsonBinding;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.JsonValue;
@@ -36,15 +33,10 @@
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.PropertyFieldRegistry.NFP;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.Utils;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.validator.SecurityTableWrapper;
-import de.fraunhofer.iosb.ilt.frostserver.service.UpdateMode;
-import de.fraunhofer.iosb.ilt.frostserver.util.exception.IncompleteEntityException;
-import de.fraunhofer.iosb.ilt.frostserver.util.exception.NoSuchEntityException;
import de.fraunhofer.iosb.ilt.frostserver.util.user.PrincipalExtended;
import java.util.Arrays;
import java.util.List;
-import net.time4j.Moment;
import org.geolatte.geom.Geometry;
-import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Name;
@@ -54,8 +46,6 @@
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultDataType;
import org.jooq.impl.SQLDataType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
public class TableImpLocations extends StaTableAbstract {
@@ -69,7 +59,6 @@ public class TableImpLocations extends StaTableAbstract {
public static final String NAME_COL_NAME = "NAME";
public static final String NAME_COL_PROPERTIES = "PROPERTIES";
- private static final Logger LOGGER = LoggerFactory.getLogger(TableImpLocations.class.getName());
private static final long serialVersionUID = -806078255;
/**
@@ -188,89 +177,12 @@ public void initProperties(final EntityFactories entityFactories) {
pfReg.addEntryMap(ModelRegistry.EP_PROPERTIES, table -> table.colProperties);
pfReg.addEntry(pluginCoreModel.npThingsLocation, TableImpLocations::getId);
pfReg.addEntry(pluginCoreModel.npHistoricalLocationsLocation, TableImpLocations::getId);
- }
-
- @Override
- protected void updateNavigationPropertySet(Entity location, EntitySet linkedSet, JooqPersistenceManager pm, UpdateMode updateMode) throws IncompleteEntityException, NoSuchEntityException {
- EntityType linkedEntityType = linkedSet.getEntityType();
- ModelRegistry modelRegistry = getModelRegistry();
- if (linkedEntityType.equals(pluginCoreModel.etThing)) {
- Object locationId = location.getPrimaryKeyValues().get(0);
- DSLContext dslContext = pm.getDslContext();
- EntityFactories entityFactories = pm.getEntityFactories();
- final TableCollection tables = getTables();
- TableImpThingsLocations ttl = tables.getTableForClass(TableImpThingsLocations.class);
-
- // Maybe Create new Things and link them to this Location.
- boolean admin = PrincipalExtended.getLocalPrincipal().isAdmin();
- for (Entity t : linkedSet) {
- if (updateMode.createAndLinkNew) {
- entityFactories.entityExistsOrCreate(pm, t, updateMode);
- } else if (!entityFactories.entityExists(pm, t, admin)) {
- throw new NoSuchEntityException("Thing not found.");
- }
-
- Object thingId = t.getPrimaryKeyValues().get(0);
-
- // Unlink old Locations from Thing.
- long delCount = dslContext.delete(ttl)
- .where(((TableField) ttl.getThingId()).eq(thingId))
- .execute();
- LOGGER.debug(EntityFactories.UNLINKED_L_FROM_T, delCount, thingId);
-
- // Link new Location to thing.
- dslContext.insertInto(ttl)
- .set((TableField) ttl.getThingId(), thingId)
- .set(ttl.getLocationId(), locationId)
- .execute();
- LOGGER.debug(EntityFactories.LINKED_L_TO_T, locationId, thingId);
-
- // Create HistoricalLocation for Thing
- TableImpHistLocations qhl = tables.getTableForClass(TableImpHistLocations.class);
- Object histLocationId = dslContext.insertInto(qhl)
- .set((TableField) qhl.getThingId(), thingId)
- .set(qhl.time, Moment.nowInSystemTime())
- .returningResult(qhl.getId())
- .fetchOne(0);
- LOGGER.debug(EntityFactories.CREATED_HL, histLocationId);
-
- // Link Location to HistoricalLocation.
- TableImpLocationsHistLocations qlhl = tables.getTableForClass(TableImpLocationsHistLocations.class);
- dslContext.insertInto(qlhl)
- .set((TableField) qlhl.getHistLocationId(), histLocationId)
- .set(qlhl.getLocationId(), locationId)
- .execute();
- LOGGER.debug(EntityFactories.LINKED_L_TO_HL, locationId, histLocationId);
-
- // Send a message about the creation of a new HL
- Entity newHl = pm.get(pluginCoreModel.etHistoricalLocation, PkValue.of(histLocationId));
- newHl.setQuery(modelRegistry.getMessageQueryGenerator().getQueryFor(newHl.getEntityType()));
- pm.getEntityChangedMessages().add(
- new EntityChangedMessage()
- .setEventType(EntityChangedMessage.Type.CREATE)
- .setEntity(newHl));
- }
- return;
- }
- super.updateNavigationPropertySet(location, linkedSet, pm, updateMode);
- }
-
- @Override
- public void delete(JooqPersistenceManager pm, PkValue entityId) throws NoSuchEntityException {
- super.delete(pm, entityId);
- final TableCollection tables = getTables();
- // Also delete all historicalLocations that no longer reference any location
- TableImpHistLocations thl = tables.getTableForClass(TableImpHistLocations.class);
- TableImpLocationsHistLocations tlhl = tables.getTableForClass(TableImpLocationsHistLocations.class);
- int count = pm.getDslContext()
- .delete(thl)
- .where(((TableField) thl.getId()).in(
- DSL.select(thl.getId())
- .from(thl)
- .leftJoin(tlhl).on(((TableField) thl.getId()).eq(tlhl.getHistLocationId()))
- .where(tlhl.getLocationId().isNull())))
- .execute();
- LOGGER.debug("Deleted {} HistoricalLocations", count);
+ registerHookPostDelete(0, new HookPostDeleteLocation());
+ HookPrePostInsertUpdateLocation hook = new HookPrePostInsertUpdateLocation();
+ registerHookPreInsert(0, hook);
+ registerHookPostInsert(0, hook);
+ registerHookPreUpdate(0, hook);
+ registerHookPostUpdate(0, hook);
}
diff --git a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpThings.java b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpThings.java
index 026fe6c3d..db56b9c42 100644
--- a/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpThings.java
+++ b/Plugins/CoreModel/src/main/java/de/fraunhofer/iosb/ilt/frostserver/plugin/coremodel/TableImpThings.java
@@ -17,12 +17,8 @@
*/
package de.fraunhofer.iosb.ilt.frostserver.plugin.coremodel;
-import de.fraunhofer.iosb.ilt.frostserver.model.EntityChangedMessage;
import de.fraunhofer.iosb.ilt.frostserver.model.EntityType;
import de.fraunhofer.iosb.ilt.frostserver.model.ModelRegistry;
-import de.fraunhofer.iosb.ilt.frostserver.model.core.Entity;
-import de.fraunhofer.iosb.ilt.frostserver.model.core.EntitySet;
-import de.fraunhofer.iosb.ilt.frostserver.model.core.PkValue;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.JooqPersistenceManager;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.JsonBinding;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.JsonValue;
@@ -32,15 +28,9 @@
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.StaTableAbstract;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.validator.SecurityTableWrapper;
-import de.fraunhofer.iosb.ilt.frostserver.service.UpdateMode;
-import de.fraunhofer.iosb.ilt.frostserver.util.exception.IncompleteEntityException;
-import de.fraunhofer.iosb.ilt.frostserver.util.exception.NoSuchEntityException;
import de.fraunhofer.iosb.ilt.frostserver.util.user.PrincipalExtended;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import net.time4j.Moment;
-import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Name;
@@ -136,76 +126,11 @@ public void initProperties(final EntityFactories entityFactories) {
pfReg.addEntry(pluginCoreModel.npDatastreamsThing, TableImpThings::getId);
pfReg.addEntry(pluginCoreModel.npHistoricalLocationsThing, TableImpThings::getId);
pfReg.addEntry(pluginCoreModel.npLocationsThing, TableImpThings::getId);
- }
-
- @Override
- protected void updateNavigationPropertySet(Entity thing, EntitySet linkedSet, JooqPersistenceManager pm, UpdateMode updateMode) throws IncompleteEntityException, NoSuchEntityException {
- final ModelRegistry modelRegistry = getModelRegistry();
- EntityType linkedEntityType = linkedSet.getEntityType();
- if (linkedEntityType.equals(pluginCoreModel.etLocation)) {
- final TableCollection tables = getTables();
- // We know a Thing has a single-valued PK.
- Object thingId = thing.getPrimaryKeyValues().get(0);
- DSLContext dslContext = pm.getDslContext();
- EntityFactories entityFactories = pm.getEntityFactories();
- TableImpThingsLocations ttl = tables.getTableForClass(TableImpThingsLocations.class);
-
- // Unlink old Locations from Thing.
- long count = dslContext.delete(ttl).where(((TableField) ttl.getThingId()).eq(thingId)).execute();
- LOGGER.debug(EntityFactories.UNLINKED_L_FROM_T, count, thingId);
-
- // Maybe Create new Locations and link them to this Thing.
- List