diff --git a/.gitignore b/.gitignore index 9f1bace10..487dc5a4a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,17 @@ pom.xml.releaseBackup release.properties /target -/FROST-Server.MQTT.Moquette/target/ -/FROST-Server.SQL.PGLong/target/ +/FROST-Server.Core/target/ /FROST-Server.SQL/target/ +/FROST-Server.SQL.PGLong/target/ /FROST-Server.SQL.PGString/target/ -/FROST-Server.Core/target/ -/FROST-Server.HTTP/target/ /FROST-Server.SQL.PGUuid/target/ +/FROST-Server.HTTP/target/ +/FROST-Server.HTTP.Common/target/ +/FROST-Server.MQTT/target/ +/FROST-Server.MQTT.Moquette/target/ +/FROST-Server.MQTTP/target/ + +FROST-Server.HTTP/keycloak.json +FROST-Server.MQTT/FrostMqtt.properties +logs diff --git a/.travis.yml b/.travis.yml index 80114475a..3f51944b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,21 +5,71 @@ jdk: branches: only: - master + - actuation - "/^v[0-9]+(\\.[0-9]+)+/" -after_success: -- test "${TRAVIS_PULL_REQUEST}" == "false" && test "${TRAVIS_TAG}" != "" && mvn deploy --settings travis-settings.xml -- mvn dockerfile:build -pl FROST-Server.HTTP -- mvn dockerfile:tag@tag-version -pl FROST-Server.HTTP -- mvn dockerfile:push@push-latest -Ddockerfile.useMavenSettingsForAuth=true -pl FROST-Server.HTTP --settings travis-settings.xml -- mvn dockerfile:push@push-version -Ddockerfile.useMavenSettingsForAuth=true -pl FROST-Server.HTTP --settings travis-settings.xml +install: + - wget http://storage.googleapis.com/kubernetes-helm/helm-${HELM_VERSION}-linux-amd64.tar.gz -O /tmp/helm.tar.gz + - tar xzf /tmp/helm.tar.gz -C /tmp --strip-components=1 + - chmod +x /tmp/helm + - /tmp/helm init --client-only + - chmod +x ./helm/build.sh + - chmod +x ./helm/upload.sh + - chmod +x ./travis-deploy.sh + - chmod +x ./travis-deploy-docker.sh + - chmod +x ./setBranchVariable.sh + - source ./setBranchVariable.sh + +stages: + - compile + - test + - deploy + +jobs: + include: + - stage: compile + name: "Maven build" + script: + - mvn package + - mvn org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar + - stage: compile + name: "Helm build" + script: + - ./helm/build.sh + - stage: test + name: "Maven test" + script: + - mvn test + - stage: deploy + name: "Deploy Docker" + script: + - ./travis-deploy-docker.sh + - stage: deploy + name: "Deploy helm chart" + script: + - ./helm/upload.sh + - stage: deploy + name: "Maven deploy version" + script: + - ./travis-deploy.sh + sudo: required services: - docker +addons: + sonarcloud: + organization: "fraunhofer-iosb" + +cache: + directories: + - '$HOME/.m2/repository' + - '$HOME/.sonar/cache' + env: global: + - HELM_VERSION="v2.9.1" # travis encrypt BINTRAY_USER=[username] - secure: pfl5pPDTyRB20+dX2PvVGChQoAVRYKiMefXZWX+tvosw40HR/XJ0cL+8skTVe6rJklKvnanWDUZUTJg1bRlq5AygHdF+g76dejaD95EcoMz/cecbxlXyAT+CISEdgzhuTpaVDEP653SCCQlUNyrJQEaROmfc/GxY9nQENZTeLxjQde+/JM1apktH0uIKvBfK7sZrdy9wNjO99fZqD3S6gVRdLLos/jha/f1rpcktnqxnF8Q6OQpmqGUiuuloIh4IYBQg3xcl/1tPauhnVqCdeMrYdLIeLEIM4U405UdOfPthvQkMHfrIkdA0ZZTaX8bgdDMebJc3+FtZSPL42PBC5O6/dQPoyp9iU86wbUnxX46pkQVTk5NyRWul0a930QjYeINdvfvg7Zh5WgBY7l42iSogknRzh5w8LHD5TTVY2jT825inuCV8aR5NHw4jguWBzeNrK5ZrXQk/J6hVxu16obd5OZe3/yw1qStGIxAOnda0LH8Y2jdT8ZQntD+FNv7nSWdUGcGxrMyaSH7PJisBQCvh8NghAmuwfN72zVM9UiDQPelHRdeUDLpTp7laHrbRKgb1rUfRLaopeLw3ORq0EyBtML5OqKEW8QHdkrgkAEYBUClu+9JN7NmttH1XGTIE5ZWm/SBd+iENAlElCh3hjWdCbjVeDF8gDoddS+3LHBg= # travis encrypt BINTRAY_API_KEY=[api key] @@ -28,3 +78,5 @@ env: - secure: P+z967jQUEzWJkAtCUMWotUr2Ft/FkKYHJmXK2/LlzsFFSB7HP+wOlxLNVMFauek1M5ZZ5jyt1s0nvmftSP4STSIfE63I05Wkjz9A5rrmG4SzUvYFF5mDOS5fa8YTNgnZhBUBP2Ip6WtVnlbbi+bxK44GJFF70ymofWAy+uEmJ+qQFf+PMYa8CS4gY3jGrV/YCY/a3fl8tL240VdjDL0Bv35MTEtMH9x3DR04mAAFlq+6cuaYq/m8eTwivNV7BcC/hz69SroWIk7RnAht6pcVT08W32hib67TXF4zt5kljxuH7hsG+trh9bZFUh2XMGXTZV0ps89WFI6/qTe0olrj++xYuRgglhhUUmpqY3npxGpsq7Z65xoK6UyInVuy+JVUHm77/QIucQY6bxpWx1NyEVUSaQpLGVXp/ABZ6n98OJcosf1e18urBVoV8F2rZYNcRLtuHwmw1rtpI2PCZOvc4ewnJy6hGKmoDa1CzK7Xj8hQ9wtKR9+0m9x7X+qZ/vrfaN7jJ7uLPfgzgZlip4mWTkFw6GSeso6iYjgv+ARHPFwfe46M3K1QzNq5CRLrn++f2r2doWBfpYtd06YYKH8JT8u/q1IFL8nrqwJ6jT22CXO06+EZV5V5ZSEtrrK9/kII81LLG4xIFyvZ5C2csVLdzGg2Mdcna+UZU6zCbGvT3g= # travis encrypt DOCKER_PASSWORD=[username] - secure: tMi5jaBO/iaK/Lc3x5WTXqgGfP35v4KZFvc5AD59qEETI1GmQ22e0maqq5YZfZWz5g3ZzS2irWRzKfrYln1lgx2LJ1AI4qHW5CQI0dlEj7OIxzK8FXlJz5tkovlfIHkniL5XyFc4eUSo+d878h8CyNRDoKQlGQie3rP0oaMl5r+CfhsdA4FRxWFY3xerlxUF20XDceVOc2tW7+HB7ROf/jFzlSxMqShbmHOvmcBxMYChPEv9xDsV16LJ+dHAg5H+DNBwFwaoMzFRmFTECrQD5xroo+Jn9eoDvWQ1OXJNQCRulbdZcq/rUOE7YQqjTuO3/aGocNPNH+5GUCf/nDSfweCPoBtaaZU7anWfJbzP9DwA6bQ490mBzgSas8n/ar0zgD8+sjwcghcPVPqH7+ZLXvw/GRs+FVd4eFs2iMgA8CoIGPqs20sYj1PGULCLeQnCDGujQnAfXMsmd49pRlOAnP4d5dgchETK2jf05UPX3Ou65yJght2NoRl5x+NPo8iei/nGtGIM8hSzyb0nPt6TiznYPGB5p0Fob2r23H/uyFVgKriEstYR7STCb1asKFZrARmVhYmngmsO/UX8poSj500ipSi5ymgVZZ/u6361Ghy38c1la62H+DgylPR3Of21DGlb11smfgfhggYJ/f7pMT1tV+Prjr+k9WhD8i2i2Mc= + # travis encrypt GITHUB_API_KEY + - secure: W3jIZLR8Dk/Wj3rhjT4AJrp95A070XhQXeQrKgQDjgxvnQEGa0/7a10RBhOWxD1zr8hlTfUsTVKG/lvFeVv8nIZuk1nBLalSXXwSH7LUXV6pL0Cus0eGDPP7BiEsh7E2Qey3C0kGdtGNW8wwm7qsM9ww+npis1WmhIir1AB0xYCIItc4ABcZTSKuOCxaEiVKXf9Gvu28QysANmk2H2QOhcl3xnWINJiNfJZhyoPg/eeZiNR9OpZ1XJZ6AQr/aTmvN+veRQwLq8tRA7Vp4312mlhb+FeTwO4AVp3Vvsl/OPOTxVNYuynIiDX84KAi8xx6qvpAdZrt9ZusnD6Vi9j/Ml3S81U/zXt05rlTsBRYUqKzrUe55tFQwrPu3oSHhH775kUCQ61/c38fGRAKznMze6s/nU2ohJvMq1VX2jKytybdZd7Hsesxl/th8UvEUk/NWx9faOVMcXK6mF0HQPR3wa62QcclUUwZAEbjA3TUct/8L3lfgo3VVA45AzqRX3cFe95I3CBcEEjnVe2YUenqUmWOhXVIRF/B/pcd1MCrdkgsHKmnEFUNPkYMn/E0m266qzDjs+Ag4MH+yIWE/6Cp4cs/KpLRLCHtp/3Qz7HOKjE3r1PEdD3BX3qdeK/QzcbbvtYbrgZ/T9CKJuoRYQoLaIx27b6bxsjZGdE7ypZkUng= diff --git a/CHANGELOG.md b/CHANGELOG.md index f8e876d2a..20a951dbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,83 @@ +# Release Version 1.9 +Version 1.9 is not released yet. + +**New Features** +* Added experimental DELETE on Collections, with filters. Allows easier data cleanup. + See https://github.com/opengeospatial/sensorthings/issues/44 +* Added experimental way to change the location of a Thing, without generating a + HistoricalLocation with a time of now(). See #66 and https://github.com/opengeospatial/sensorthings/issues/30 + + +# Release Version 1.8 +Version 1.8 was released on 2018-08-24. + +**New Features** +* Upgraded moquette to v0.11. +* Allow setting of the moquette persistent store path and storage class. +* Enabling the tomcat CorsFilter to allow cross-site-scripting can be done from environment variables. +* Added option to automatically run the liquibase database upgrade. + +**Bugfixes** +* Fixed #59, incorrect nextLink when filtering on unitOfMeasurement/name. +* Fixed `MultiDatastream.observationType` being required even though we set it automatically. +* Prioritise `persistence_db_url` over `persistence_db_jndi_datasource`. This way there is no longer the need to add an empty environment variable `persistence_db_jndi_datasource` for the HTTP and MQTTP component when configuring using environment variables. +* Fixed string ids in next- and selfLink not being urlEncoded. + + +# Release Version 1.7 +Version 1.7 was released on 2018-07-02. + +**New Features** +* Observation.result can be explicitly set to null. This is useful in cases where + an observation did not produce a value, but the fact that an observation was attempted + must still be recorded. +* Exposed database connection options `persistence.db.conn.max`, `persistence.db.conn.idle.max`, `persistence.db.conn.idle.min` + +**Bugfixes** +* Fixed #53: Query parser not Unicode aware. +* Fixed #52: Generating FeatureOfInterest did not work for Things with multiple Location entities when some of these entities were not geoJSON. +* Fixed the 'year' function not working on interval properties. + + +# Release Version 1.6 +Version 1.6 was released on 2018-05-09. + +**New Features** +* User-defined-ids. FROST-Server can not be configured to allow the user to specify the id of created enitites. + The new setting `persistence.idGenerationMode` has three allowed values: + * `ServerGeneratedOnly`: No client defined ids allowed, database generates ids. + * `ServerAndClientGenerated`: Both, server and client generated ids, are allowed. + * `ClientGeneratedOnly`: Client has to provide @iot.id to create entities. + + Thanks to Marcel Köpke for the patch. +* Improved time handling in queries. FROST-Server can now calculate with times: + + ```/Observations?$filter=phenomenonTime gt now() sub duration'P1D' mul Datastream/properties/days``` + +* Separated the MQTT and HTTP parts of the server. + The MQTT and HTTP parts of the server are now separated in to stand-alone programs: + * FROST-Server.HTTP: contains a web-app handling the HTTP part of the server. + * FROST-Server.MQTT: contains a java application handling the MQTT part of the server. + * FROST-Server.MQTTP: contains a web-app combining HTTP and MQTT, like it was before. + + There can be multiple MQTT and HTTP instances using the same database, to allow for horizontal + scaling on a cloud infrastructure. The instances communicate over a pluggable message bus. +* There are now three docker images: + * The stand-alone HTTP package: fraunhoferiosb/frost-server-http. + * The stand-alone MQTT package: fraunhoferiosb/frost-server-mqtt. + * The all-in-one package: fraunhoferiosb/frost-server. + + An example configuration for docker-compose can be found as docker-compose-separated.yaml, + that shows how the HTTP and MQTT packages can be started separately, with an MQTT message bus + for communication between the HTTP and MQTT instances. +* All configuration parameters can now be overridden using environment variables. + +**Bugfixes** +* Fixed service prefix in default config file. +* Fixed Tomcat breaking selfLinks for ids that are URLs. +* Fixed $select not working for @iot.id, in MQTT. +* Fixed #48: creation in Observations in MultiDatastreams using DataArray formatting fails. + # Release Version 1.5 Version 1.5 was released on 2018-02-15. diff --git a/FROST-Server.Core/pom.xml b/FROST-Server.Core/pom.xml index 37a95865f..1a0eb57a5 100644 --- a/FROST-Server.Core/pom.xml +++ b/FROST-Server.Core/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT ../pom.xml FROST-Server.Core @@ -55,12 +55,27 @@ slf4j-api ${slf4j-api.version} + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + ${paho.version} + ch.qos.logback logback-classic ${logback.version} test + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + com.google.guava + guava + ${guava.version} + @@ -68,7 +83,7 @@ org.codehaus.mojo javacc-maven-plugin - 2.6 + ${javacc-maven-plugin.version} jjtree-javacc @@ -81,14 +96,14 @@ net.java.dev.javacc javacc - 6.1.2 + ${javacc.version} org.apache.maven.plugins maven-compiler-plugin - 3.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} @@ -97,18 +112,10 @@ - - org.apache.maven.plugins - maven-war-plugin - 2.3 - - false - - org.apache.maven.plugins maven-dependency-plugin - 2.6 + ${maven-dependency-plugin.version} validate @@ -130,6 +137,32 @@ + + pl.project13.maven + git-commit-id-plugin + ${git-commit-id-plugin.version} + + + get-the-git-infos + + revision + + + + + ${project.basedir}/../.git + git + false + true + ${project.build.outputDirectory}/git.json + json + + false + false + -dirty + + + diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DataArrayValue.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DataArrayValue.java index 33c175392..d6d60b1a5 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DataArrayValue.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DataArrayValue.java @@ -104,12 +104,7 @@ public void setDataArray(List> dataArray) { @Override public int hashCode() { - int hash = 7; - hash = 29 * hash + Objects.hashCode(this.datastream); - hash = 29 * hash + Objects.hashCode(this.multiDatastream); - hash = 29 * hash + Objects.hashCode(this.components); - hash = 29 * hash + Objects.hashCode(this.dataArray); - return hash; + return Objects.hash(datastream, multiDatastream, components, dataArray); } @Override @@ -133,10 +128,7 @@ public boolean equals(Object obj) { if (!Objects.equals(this.components, other.components)) { return false; } - if (!Objects.equals(this.dataArray, other.dataArray)) { - return false; - } - return true; + return Objects.equals(this.dataArray, other.dataArray); } public static String dataArrayIdFor(Observation observation) { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DefaultResultFormater.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DefaultResultFormater.java index a52b59b23..fa88f9aed 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DefaultResultFormater.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/formatter/DefaultResultFormater.java @@ -1,31 +1,32 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.formatter; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; import de.fraunhofer.iosb.ilt.sta.model.Observation; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.serialize.EntityFormatter; import de.fraunhofer.iosb.ilt.sta.util.VisibilityHelper; import java.io.IOException; import java.util.ArrayList; @@ -42,9 +43,6 @@ */ public class DefaultResultFormater implements ResultFormatter { - public DefaultResultFormater() { - } - @Override public String format(ResourcePath path, Query query, Object result, boolean useAbsoluteNavigationLinks) { String entityJsonString = ""; @@ -53,25 +51,25 @@ public String format(ResourcePath path, Query query, Object result, boolean useA Entity entity = (Entity) result; VisibilityHelper.applyVisibility(entity, path, query, useAbsoluteNavigationLinks); - entityJsonString = new EntityFormatter().writeEntity(entity); + entityJsonString = EntityFormatter.writeEntity(entity); } else if (EntitySet.class.isAssignableFrom(result.getClass())) { EntitySet entitySet = (EntitySet) result; - if (query.getFormat() != null && query.getFormat().equalsIgnoreCase("dataarray") && entitySet.getEntityType() == EntityType.Observation) { - return formatDataArray(path, query, entitySet, useAbsoluteNavigationLinks); + if (query.getFormat() != null && query.getFormat().equalsIgnoreCase("dataarray") && entitySet.getEntityType() == EntityType.OBSERVATION) { + return formatDataArray(path, query, entitySet); } VisibilityHelper.applyVisibility(entitySet, path, query, useAbsoluteNavigationLinks); - entityJsonString = new EntityFormatter().writeEntityCollection(entitySet); + entityJsonString = EntityFormatter.writeEntityCollection(entitySet); } else if (path != null && path.isValue()) { if (result instanceof Map) { - entityJsonString = new EntityFormatter().writeObject(result); + entityJsonString = EntityFormatter.writeObject(result); } else if (result instanceof Id) { entityJsonString = ((Id) result).getValue().toString(); } else { entityJsonString = result.toString(); } } else { - entityJsonString = new EntityFormatter().writeObject(result); + entityJsonString = EntityFormatter.writeObject(result); } } catch (IOException ex) { Logger.getLogger(DefaultResultFormater.class.getName()).log(Level.SEVERE, null, ex); @@ -104,37 +102,37 @@ public VisibleComponents(boolean allValue) { } public VisibleComponents(Set select) { - id = select.contains(EntityProperty.Id); - phenomenonTime = select.contains(EntityProperty.PhenomenonTime); - result = select.contains(EntityProperty.Result); - resultTime = select.contains(EntityProperty.ResultTime); - resultQuality = select.contains(EntityProperty.ResultQuality); - validTime = select.contains(EntityProperty.ValidTime); - parameters = select.contains(EntityProperty.Parameters); + id = select.contains(EntityProperty.ID); + phenomenonTime = select.contains(EntityProperty.PHENOMENONTIME); + result = select.contains(EntityProperty.RESULT); + resultTime = select.contains(EntityProperty.RESULTTIME); + resultQuality = select.contains(EntityProperty.RESULTQUALITY); + validTime = select.contains(EntityProperty.VALIDTIME); + parameters = select.contains(EntityProperty.PARAMETERS); } public List getComponents() { List components = new ArrayList<>(); if (id) { - components.add(EntityProperty.Id.name); + components.add(EntityProperty.ID.entitiyName); } if (phenomenonTime) { - components.add(EntityProperty.PhenomenonTime.name); + components.add(EntityProperty.PHENOMENONTIME.entitiyName); } if (result) { - components.add(EntityProperty.Result.name); + components.add(EntityProperty.RESULT.entitiyName); } if (resultTime) { - components.add(EntityProperty.ResultTime.name); + components.add(EntityProperty.RESULTTIME.entitiyName); } if (resultQuality) { - components.add(EntityProperty.ResultQuality.name); + components.add(EntityProperty.RESULTQUALITY.entitiyName); } if (validTime) { - components.add(EntityProperty.ValidTime.name); + components.add(EntityProperty.VALIDTIME.entitiyName); } if (parameters) { - components.add(EntityProperty.Parameters.name); + components.add(EntityProperty.PARAMETERS.entitiyName); } return components; } @@ -166,7 +164,7 @@ public List fromObservation(Observation o) { } } - public String formatDataArray(ResourcePath path, Query query, EntitySet entitySet, boolean useAbsoluteNavigationLinks) throws IOException { + public String formatDataArray(ResourcePath path, Query query, EntitySet entitySet) throws IOException { VisibleComponents visComps; if (query == null || query.getSelect().isEmpty()) { visComps = new VisibleComponents(true); @@ -178,11 +176,10 @@ public String formatDataArray(ResourcePath path, Query query, EntitySet dataArraySet = new LinkedHashMap<>(); for (Observation obs : entitySet) { String dataArrayId = DataArrayValue.dataArrayIdFor(obs); - DataArrayValue dataArray = dataArraySet.get(dataArrayId); - if (dataArray == null) { - dataArray = new DataArrayValue(path, obs, components); - dataArraySet.put(dataArrayId, dataArray); - } + DataArrayValue dataArray = dataArraySet.computeIfAbsent( + dataArrayId, + k -> new DataArrayValue(path, obs, components) + ); dataArray.getDataArray().add(visComps.fromObservation(obs)); } @@ -195,8 +192,7 @@ public String formatDataArray(ResourcePath path, Query query, EntitySet. */ package de.fraunhofer.iosb.ilt.sta.formatter; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/EntityParser.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/EntityParser.java similarity index 61% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/EntityParser.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/EntityParser.java index 3ac1312de..76309d914 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/EntityParser.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/EntityParser.java @@ -15,16 +15,19 @@ * 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.sta.deserialize; +package de.fraunhofer.iosb.ilt.sta.json.deserialize; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.CustomDeserializationManager; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.CustomEntityDeserializer; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.geojson.GeoJsonDeserializier; import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayValue; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.custom.CustomDeserializationManager; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.custom.CustomEntityChangedMessageDeserializer; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.custom.CustomEntityDeserializer; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.custom.GeoJsonDeserializier; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntitySetCamelCaseNamingStrategy; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; import de.fraunhofer.iosb.ilt.sta.model.Datastream; import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; @@ -37,21 +40,12 @@ import de.fraunhofer.iosb.ilt.sta.model.core.Entity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.model.mixin.DatastreamMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.FeatureOfInterestMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.HistoricalLocationMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.LocationMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.MultiDatastreamMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.ObservationMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.ObservedPropertyMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.SensorMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.ThingMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.UnitOfMeasurementMixIn; -import de.fraunhofer.iosb.ilt.sta.serialize.EntitySetCamelCaseNamingStrategy; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.mixin.MixinUtils; import java.io.IOException; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Allows parsing of STA entities from JSON. Fails on unknown properties in the @@ -68,37 +62,97 @@ public class EntityParser { public static final TypeReference listOfDataArrayValue = new TypeReference>() { // Empty by design. }; - private final ObjectMapper mapper; + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(EntityParser.class); - public EntityParser(Class idClass) { + private static ObjectMapper mainMapper; + private static Class mainIdClass; + + private static ObjectMapper simpleObjectMapper; + + /** + * Get an object mapper for the given id Class. If the id class is the same + * as for the first call, the cached mapper is returned. + * + * @param idClass The id class to use for this mapper. + * @return The cached or created object mapper. + */ + private static ObjectMapper getObjectMapper(Class idClass) { + if (mainMapper == null) { + initMainObjectMapper(idClass); + } + if (mainIdClass != idClass) { + LOGGER.warn("Object Mapper requested with different id class. {} instead of {}", idClass, mainIdClass); + return createObjectMapper(idClass); + } + return mainMapper; + } + + /** + * Initialise the main object mapper. + * + * @param idClass The id class to use for the main object mapper. + */ + private static synchronized void initMainObjectMapper(Class idClass) { + if (mainMapper != null) { + return; + } + mainMapper = createObjectMapper(idClass); + mainIdClass = idClass; + } + + /** + * Create a new object mapper for the given id Class. + * + * @param idClass The id class to use for this mapper. + * @return The created object mapper. + */ + private static ObjectMapper createObjectMapper(Class idClass) { GeoJsonDeserializier geoJsonDeserializier = new GeoJsonDeserializier(); - for (String encodingType : GeoJsonDeserializier.encodings) { + for (String encodingType : GeoJsonDeserializier.ENCODINGS) { CustomDeserializationManager.getInstance().registerDeserializer(encodingType, geoJsonDeserializier); } - mapper = new ObjectMapper() + ObjectMapper mapper = new ObjectMapper() .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); - //mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + mapper.setPropertyNamingStrategy(new EntitySetCamelCaseNamingStrategy()); - mapper.addMixIn(Datastream.class, DatastreamMixIn.class); - mapper.addMixIn(MultiDatastream.class, MultiDatastreamMixIn.class); - mapper.addMixIn(FeatureOfInterest.class, FeatureOfInterestMixIn.class); - mapper.addMixIn(HistoricalLocation.class, HistoricalLocationMixIn.class); - mapper.addMixIn(Location.class, LocationMixIn.class); - mapper.addMixIn(Observation.class, ObservationMixIn.class); - mapper.addMixIn(ObservedProperty.class, ObservedPropertyMixIn.class); - mapper.addMixIn(Sensor.class, SensorMixIn.class); - mapper.addMixIn(Thing.class, ThingMixIn.class); - mapper.addMixIn(UnitOfMeasurement.class, UnitOfMeasurementMixIn.class); + + MixinUtils.addMixins(mapper); + SimpleModule module = new SimpleModule(); module.addAbstractTypeMapping(EntitySet.class, EntitySetImpl.class); module.addAbstractTypeMapping(Id.class, idClass); module.addDeserializer(Location.class, new CustomEntityDeserializer(Location.class)); module.addDeserializer(FeatureOfInterest.class, new CustomEntityDeserializer(FeatureOfInterest.class)); module.addDeserializer(Sensor.class, new CustomEntityDeserializer(Sensor.class)); - // TODO Datastream.observationType supplies encodingType for Observation.result. How to deserialize content when ne Observation is inserted? - //module.addDeserializer(Datastream.class, new CustomEntityDeserializer(Datastream.class)); + module.addDeserializer(EntityChangedMessage.class, new CustomEntityChangedMessageDeserializer()); mapper.registerModule(module); + return mapper; + } + + /** + * get an ObjectMapper for generic, non-STA use. + * + * @return an ObjectMapper for generic, non-STA use. + */ + public static ObjectMapper getSimpleObjectMapper() { + if (simpleObjectMapper == null) { + simpleObjectMapper = new ObjectMapper() + .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); + } + return simpleObjectMapper; + } + + /** + * The objectMapper for this instance of EntityParser. + */ + private final ObjectMapper mapper; + + public EntityParser(Class idClass) { + mapper = getObjectMapper(idClass); } public Datastream parseDatastream(String value) throws IOException { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeInstantDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeInstantDeserializer.java similarity index 90% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeInstantDeserializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeInstantDeserializer.java index 98f195edd..269dabb85 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeInstantDeserializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeInstantDeserializer.java @@ -15,10 +15,9 @@ * 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.sta.deserialize; +package de.fraunhofer.iosb.ilt.sta.json.deserialize; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @@ -37,7 +36,7 @@ public TimeInstantDeserializer() { } @Override - public TimeInstant deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + public TimeInstant deserialize(JsonParser jp, DeserializationContext dc) throws IOException { return TimeInstant.parse(((JsonNode) jp.getCodec().readTree(jp)).asText()); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeIntervalDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeIntervalDeserializer.java similarity index 90% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeIntervalDeserializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeIntervalDeserializer.java index 70a9558f2..e71ebc140 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeIntervalDeserializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeIntervalDeserializer.java @@ -15,10 +15,9 @@ * 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.sta.deserialize; +package de.fraunhofer.iosb.ilt.sta.json.deserialize; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @@ -37,7 +36,7 @@ public TimeIntervalDeserializer() { } @Override - public TimeInterval deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + public TimeInterval deserialize(JsonParser jp, DeserializationContext dc) throws IOException { return TimeInterval.parse(((JsonNode) jp.getCodec().readTree(jp)).asText()); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeValueDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeValueDeserializer.java similarity index 85% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeValueDeserializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeValueDeserializer.java index 060ed2736..3c0b89b99 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/TimeValueDeserializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/TimeValueDeserializer.java @@ -15,10 +15,9 @@ * 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.sta.deserialize; +package de.fraunhofer.iosb.ilt.sta.json.deserialize; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @@ -28,8 +27,9 @@ import java.io.IOException; /** - * Helper for deserialization of TimeValue objects from JSON. May not work properly in every case as deciding wether - * input is a TimeInstant or a TimeInterval is based on exceptions while parsing + * Helper for deserialization of TimeValue objects from JSON. May not work + * properly in every case as deciding wether input is a TimeInstant or a + * TimeInterval is based on exceptions while parsing * * @author jab */ @@ -40,7 +40,7 @@ public TimeValueDeserializer() { } @Override - public TimeValue deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + public TimeValue deserialize(JsonParser jp, DeserializationContext dc) throws IOException { TimeValue result; JsonNode node = jp.getCodec().readTree(jp); try { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/ToStringDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/ToStringDeserializer.java similarity index 89% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/ToStringDeserializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/ToStringDeserializer.java index 653c2013a..0fe22fa33 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/ToStringDeserializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/ToStringDeserializer.java @@ -15,10 +15,9 @@ * 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.sta.deserialize; +package de.fraunhofer.iosb.ilt.sta.json.deserialize; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @@ -36,7 +35,7 @@ public ToStringDeserializer() { } @Override - public Object deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + public Object deserialize(JsonParser jp, DeserializationContext dc) throws IOException { TreeNode tree = jp.getCodec().readTree(jp); return tree.toString(); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomDeserializationManager.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomDeserializationManager.java similarity index 96% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomDeserializationManager.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomDeserializationManager.java index a0f38c575..52523cee7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomDeserializationManager.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomDeserializationManager.java @@ -15,7 +15,7 @@ * 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.sta.deserialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.deserialize.custom; import java.util.HashMap; import java.util.Map; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomDeserializer.java similarity index 93% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomDeserializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomDeserializer.java index dc588f257..9591d0ea0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomDeserializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomDeserializer.java @@ -15,7 +15,7 @@ * 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.sta.deserialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.deserialize.custom; import java.io.IOException; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomEntityChangedMessageDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomEntityChangedMessageDeserializer.java new file mode 100644 index 000000000..3a909639f --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomEntityChangedMessageDeserializer.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 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.sta.json.deserialize.custom; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author scf + */ +public class CustomEntityChangedMessageDeserializer extends JsonDeserializer { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(CustomEntityChangedMessageDeserializer.class); + + @Override + public EntityChangedMessage deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { + EntityChangedMessage message = new EntityChangedMessage(); + ObjectMapper mapper = (ObjectMapper) parser.getCodec(); + JsonNode obj = mapper.readTree(parser); + Iterator> i = obj.fields(); + EntityType type = null; + JsonNode entityJson = null; + while (i.hasNext()) { + Map.Entry next = i.next(); + String name = next.getKey(); + JsonNode value = next.getValue(); + switch (name.toLowerCase()) { + case "eventtype": + message.setEventType(EntityChangedMessage.Type.valueOf(value.asText())); + break; + + case "entitytype": + type = EntityType.valueOf(value.asText()); + break; + + case "epfields": + for (JsonNode field : value) { + String fieldName = field.asText(); + message.addEpField(EntityProperty.valueOf(fieldName)); + } + break; + + case "npfields": + for (JsonNode field : value) { + String fieldName = field.asText(); + message.addNpField(NavigationProperty.valueOf(fieldName)); + } + break; + + case "entity": + entityJson = value; + break; + + default: + LOGGER.warn("Unknown field in message: {}", name); + break; + } + } + if (type == null || entityJson == null) { + throw new IllegalArgumentException("Message json with no type or no entity."); + } + message.setEntity(parseEntity(mapper, entityJson, type)); + return message; + } + + private static Entity parseEntity(ObjectMapper mapper, JsonNode entityJson, EntityType entityType) { + Entity entity = mapper.convertValue(entityJson, entityType.getImplementingClass()); + if (entity instanceof Observation) { + Observation observation = (Observation) entity; + if (observation.getResultTime() == null) { + observation.setResultTime(new TimeInstant(null)); + } + } + for (NavigationProperty property : entityType.getNavigationEntities()) { + Object parentObject = entity.getProperty(property); + if (parentObject instanceof Entity) { + Entity parentEntity = (Entity) parentObject; + parentEntity.setExportObject(false); + } + } + return entity; + } +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomEntityDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomEntityDeserializer.java similarity index 57% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomEntityDeserializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomEntityDeserializer.java index a8a5ab868..fdef0be26 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/CustomEntityDeserializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/CustomEntityDeserializer.java @@ -15,10 +15,9 @@ * 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.sta.deserialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.deserialize.custom; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -27,8 +26,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; +import de.fraunhofer.iosb.ilt.sta.json.serialize.custom.CustomSerialization; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.serialize.custom.CustomSerialization; import java.io.IOException; import java.lang.annotation.Annotation; import java.util.Iterator; @@ -50,7 +49,7 @@ public CustomEntityDeserializer(Class clazz) { } @Override - public T deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException { + public T deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { Entity result; try { result = clazz.newInstance(); @@ -60,16 +59,15 @@ public T deserialize(JsonParser parser, DeserializationContext ctxt) throws IOEx // need to make subclass of this class for every Entity subclass with custom field to get expected class!!! BeanDescription beanDescription = ctxt.getConfig().introspect(ctxt.constructType(clazz)); ObjectMapper mapper = (ObjectMapper) parser.getCodec(); - JsonNode obj = (JsonNode) mapper.readTree(parser); + JsonNode obj = mapper.readTree(parser); List properties = beanDescription.findProperties(); Iterator> i = obj.fields(); // First check if we know all properties that are present. if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) { - for (; i.hasNext();) { + while (i.hasNext()) { Map.Entry next = i.next(); String fieldName = next.getKey(); - JsonNode field = next.getValue(); Optional findFirst = properties.stream().filter(p -> p.getName().equals(fieldName)).findFirst(); if (!findFirst.isPresent()) { throw new UnrecognizedPropertyException(parser, "Unknown field: " + fieldName, parser.getCurrentLocation(), clazz, fieldName, null); @@ -78,34 +76,39 @@ public T deserialize(JsonParser parser, DeserializationContext ctxt) throws IOEx } for (BeanPropertyDefinition classProperty : properties) { - if (obj.has(classProperty.getName())) { - // property is present in class and json - Annotation annotation = classProperty.getAccessor().getAnnotation(CustomSerialization.class); - if (annotation != null) { - // property has custom annotation - // check if encoding property is also present in json (and also in class itself for sanity reasons) - CustomSerialization customAnnotation = (CustomSerialization) annotation; - Optional encodingClassProperty = properties.stream().filter(p -> p.getName().equals(customAnnotation.encoding())).findFirst(); - if (!encodingClassProperty.isPresent()) { - throw new IOException("Error deserializing JSON as class '" + clazz.toString() + "' \n" - + "Reason: field '" + customAnnotation.encoding() + "' specified by annotation as encoding field is not defined in class!"); - } - String customEncoding = null; - if (obj.has(customAnnotation.encoding())) { - customEncoding = obj.get(customAnnotation.encoding()).asText(); - } - Object customDeserializedValue = CustomDeserializationManager.getInstance() - .getDeserializer(customEncoding) - .deserialize(mapper.writeValueAsString(obj.get(classProperty.getName()))); - classProperty.getMutator().setValue(result, customDeserializedValue); - } else { - // TODO type identificatin is not safe beacuase may ne be backed by field. Rather do multiple checks - Object value = mapper.readValue(mapper.writeValueAsString(obj.get(classProperty.getName())), classProperty.getField().getType()); - classProperty.getMutator().setValue(result, value); + deserialiseProperty(obj, classProperty, properties, mapper, result); + } + return (T) result; + } + + private void deserialiseProperty(JsonNode obj, BeanPropertyDefinition classProperty, List properties, ObjectMapper mapper, Entity result) throws IOException { + if (obj.has(classProperty.getName())) { + // property is present in class and json + Annotation annotation = classProperty.getAccessor().getAnnotation(CustomSerialization.class); + if (annotation == null) { + // TODO type identificatin is not safe beacuase may not be backed by field. Rather do multiple checks + // TODO this seems inefficient. Use mapper.convertValue ? + Object value = mapper.readValue(mapper.writeValueAsString(obj.get(classProperty.getName())), classProperty.getField().getType()); + classProperty.getMutator().setValue(result, value); + } else { + // property has custom annotation + // check if encoding property is also present in json (and also in class itself for sanity reasons) + CustomSerialization customAnnotation = (CustomSerialization) annotation; + Optional encodingClassProperty = properties.stream().filter(p -> p.getName().equals(customAnnotation.encoding())).findFirst(); + if (!encodingClassProperty.isPresent()) { + throw new IOException("Error deserializing JSON as class '" + clazz.toString() + "' \n" + + "Reason: field '" + customAnnotation.encoding() + "' specified by annotation as encoding field is not defined in class!"); + } + String customEncoding = null; + if (obj.has(customAnnotation.encoding())) { + customEncoding = obj.get(customAnnotation.encoding()).asText(); } + Object customDeserializedValue = CustomDeserializationManager.getInstance() + .getDeserializer(customEncoding) + .deserialize(mapper.writeValueAsString(obj.get(classProperty.getName()))); + classProperty.getMutator().setValue(result, customDeserializedValue); } } - return (T) result; } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/DefaultDeserializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/DefaultDeserializer.java similarity index 75% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/DefaultDeserializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/DefaultDeserializer.java index 675828418..b779ae0bf 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/DefaultDeserializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/DefaultDeserializer.java @@ -15,10 +15,9 @@ * 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.sta.deserialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.deserialize.custom; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; import java.io.IOException; /** @@ -29,9 +28,7 @@ public class DefaultDeserializer implements CustomDeserializer { @Override public Object deserialize(String json) throws IOException { - return new ObjectMapper() - .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) - .readValue(json, Object.class); + return EntityParser.getSimpleObjectMapper().readValue(json, Object.class); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/geojson/GeoJsonDeserializier.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/GeoJsonDeserializier.java similarity index 79% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/geojson/GeoJsonDeserializier.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/GeoJsonDeserializier.java index 0b185c761..d5c7a06ca 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/deserialize/custom/geojson/GeoJsonDeserializier.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/deserialize/custom/GeoJsonDeserializier.java @@ -15,10 +15,9 @@ * 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.sta.deserialize.custom.geojson; +package de.fraunhofer.iosb.ilt.sta.json.deserialize.custom; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.CustomDeserializer; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; import java.io.IOException; import java.util.Arrays; import java.util.Collections; @@ -32,13 +31,13 @@ */ public class GeoJsonDeserializier implements CustomDeserializer { - public static final Set encodings = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + public static final Set ENCODINGS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( "application/geo+json", "application/vnd.geo+json" ))); @Override public Object deserialize(String json) throws IOException { - return new ObjectMapper().readValue(json, GeoJsonObject.class); + return EntityParser.getSimpleObjectMapper().readValue(json, GeoJsonObject.class); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/DataArrayResultSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/DataArrayResultSerializer.java similarity index 90% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/DataArrayResultSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/DataArrayResultSerializer.java index eddea7708..ace4fb8b9 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/DataArrayResultSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/DataArrayResultSerializer.java @@ -15,10 +15,9 @@ * 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.sta.serialize; +package de.fraunhofer.iosb.ilt.sta.json.serialize; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayResult; @@ -31,7 +30,7 @@ public class DataArrayResultSerializer extends JsonSerializer { @Override - public void serialize(DataArrayResult value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + public void serialize(DataArrayResult value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); long count = value.getCount(); if (count >= 0) { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/DataArrayValueSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/DataArrayValueSerializer.java similarity index 92% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/DataArrayValueSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/DataArrayValueSerializer.java index 60e4bcb30..6dfc37f8b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/DataArrayValueSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/DataArrayValueSerializer.java @@ -15,10 +15,9 @@ * 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.sta.serialize; +package de.fraunhofer.iosb.ilt.sta.json.serialize; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayValue; @@ -33,7 +32,7 @@ public class DataArrayValueSerializer extends JsonSerializer { @Override - public void serialize(DataArrayValue value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + public void serialize(DataArrayValue value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); Datastream datastream = value.getDatastream(); if (datastream != null) { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntityFormatter.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntityFormatter.java new file mode 100644 index 000000000..f90a5677c --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntityFormatter.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 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.sta.json.serialize; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayResult; +import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayValue; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.custom.GeoJsonDeserializier; +import de.fraunhofer.iosb.ilt.sta.json.serialize.custom.CustomSerializationManager; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; +import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; +import de.fraunhofer.iosb.ilt.sta.model.Location; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; +import de.fraunhofer.iosb.ilt.sta.model.Sensor; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import de.fraunhofer.iosb.ilt.sta.model.ext.EntitySetResult; +import de.fraunhofer.iosb.ilt.sta.model.mixin.MixinUtils; +import java.io.IOException; + +/** + * Enables serialization of entities as JSON. + * + * @author jab + */ +public class EntityFormatter { + + private static ObjectMapper objectMapperInstance; + + public static ObjectMapper getObjectMapper() { + if (objectMapperInstance == null) { + initObjectMapper(); + } + return objectMapperInstance; + } + + private static synchronized void initObjectMapper() { + if (objectMapperInstance == null) { + objectMapperInstance = createObjectMapper(); + } + } + + private static ObjectMapper createObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + mapper.setPropertyNamingStrategy(new EntitySetCamelCaseNamingStrategy()); + + MixinUtils.addMixins(mapper); + + SimpleModule module = new SimpleModule(); + GeoJsonSerializer geoJsonSerializer = new GeoJsonSerializer(); + for (String encodingType : GeoJsonDeserializier.ENCODINGS) { + CustomSerializationManager.getInstance().registerSerializer(encodingType, geoJsonSerializer); + } + + module.addSerializer(Entity.class, new EntitySerializer()); + module.addSerializer(EntitySetResult.class, new EntitySetResultSerializer()); + module.addSerializer(DataArrayValue.class, new DataArrayValueSerializer()); + module.addSerializer(DataArrayResult.class, new DataArrayResultSerializer()); + mapper.registerModule(module); + return mapper; + } + + private EntityFormatter() { + } + + public static String writeEntity(T entity) throws IOException { + return getObjectMapper().writeValueAsString(entity); + } + + public static String writeEntityCollection(EntitySet entityCollection) throws IOException { + return getObjectMapper().writeValueAsString(new EntitySetResult(entityCollection)); + } + + public static String writeDatastream(Datastream datastream) throws IOException { + return writeEntity(datastream); + } + + public static String writeFeatureOfInterest(FeatureOfInterest featureOfInterest) throws IOException { + return writeEntity(featureOfInterest); + } + + public static String writeHistoricalLocation(HistoricalLocation historicalLocation) throws IOException { + return writeEntity(historicalLocation); + } + + public static String writeLocation(Location location) throws IOException { + return writeEntity(location); + } + + public static String writeObservation(Observation observation) throws IOException { + return writeEntity(observation); + } + + public static String writeObservedProperty(ObservedProperty observedProperty) throws IOException { + return writeEntity(observedProperty); + } + + public static String writeSensor(Sensor sensor) throws IOException { + return writeEntity(sensor); + } + + public static String writeThing(Thing thing) throws IOException { + return writeEntity(thing); + } + + public static String writeObject(Object object) throws IOException { + return getObjectMapper().writeValueAsString(object); + } +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySerializer.java similarity index 71% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySerializer.java index cde1408f1..02acabcff 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySerializer.java @@ -15,11 +15,11 @@ * 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.sta.serialize; +package de.fraunhofer.iosb.ilt.sta.json.serialize; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; @@ -29,15 +29,17 @@ import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import static com.fasterxml.jackson.databind.ser.BeanPropertyWriter.MARKER_FOR_EMPTY; +import com.fasterxml.jackson.databind.ser.std.NullSerializer; +import de.fraunhofer.iosb.ilt.sta.json.serialize.custom.CustomSerialization; +import de.fraunhofer.iosb.ilt.sta.json.serialize.custom.CustomSerializationManager; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.NavigableElement; -import de.fraunhofer.iosb.ilt.sta.serialize.custom.CustomSerialization; -import de.fraunhofer.iosb.ilt.sta.serialize.custom.CustomSerializationManager; import java.io.IOException; import java.lang.annotation.Annotation; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.slf4j.LoggerFactory; @@ -56,45 +58,29 @@ public class EntitySerializer extends JsonSerializer { */ private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(EntitySerializer.class); - private final List selectedProperties; - - public EntitySerializer() { - this.selectedProperties = null; - } - - public EntitySerializer(List selectedProperties) { - this.selectedProperties = selectedProperties; - } - @Override - public void serialize(Entity entity, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + public void serialize(Entity entity, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); try { BasicBeanDescription beanDescription = serializers.getConfig().introspect(serializers.constructType(entity.getClass())); List properties = beanDescription.findProperties(); + Set selectedProperties = entity.getSelectedPropertyNames(); for (BeanPropertyDefinition property : properties) { // 0. check if it should be serialized - if (selectedProperties != null) { - if (!selectedProperties.contains(property.getName())) { - continue; - } + // If not, we still have to check if it is expanded, hence no + // direct continue. + boolean selected = true; + if (selectedProperties != null && !selectedProperties.contains(property.getName())) { + selected = false; } // 1. is it a NavigableElement? if (NavigableElement.class.isAssignableFrom(property.getAccessor().getRawType())) { - Object rawValue = property.getAccessor().getValue(entity); - if (rawValue != null) { - NavigableElement value = (NavigableElement) rawValue; - // If navigation link set, output navigation link. - if (value.getNavigationLink() != null && !value.getNavigationLink().isEmpty()) { - gen.writeFieldName(property.getName() + "@iot.navigationLink"); - gen.writeString(value.getNavigationLink()); - } - // If object should not be exported, skip any further processing. - if (!value.isExportObject()) { - continue; - } - } + selected = serialiseNavigationElement(property, entity, selected, gen); } + if (!selected) { + continue; + } + // 2. check if property has CustomSerialization annotation -> use custom serializer Annotation annotation = property.getAccessor().getAnnotation(CustomSerialization.class); if (annotation != null) { @@ -107,44 +93,64 @@ public void serialize(Entity entity, JsonGenerator gen, SerializerProvider seria } else { serializeField(entity, gen, serializers, beanDescription, property); } - // 3. check if property is EntitySet than eventually write count + // 3. check if property is EntitySet than write count if needed. if (EntitySet.class.isAssignableFrom(property.getAccessor().getRawType())) { - Object rawValue = property.getAccessor().getValue(entity); - if (rawValue != null) { - EntitySet set = (EntitySet) rawValue; - long count = set.getCount(); - if (count >= 0) { - gen.writeNumberField(property.getName() + "@iot.count", count); - } - String nextLink = set.getNextLink(); - if (nextLink != null) { - gen.writeStringField(property.getName() + "@iot.nextLink", nextLink); - } - } + writeCountNextlinkForSet(property, entity, gen); } } - } catch (Exception e) { - LOGGER.error("could not serialize Entity", e); - throw new IOException("could not serialize Entity", e); + } catch (Exception exc) { + LOGGER.error("could not serialize Entity", exc); + throw new IOException("could not serialize Entity", exc); } finally { gen.writeEndObject(); } } + private void writeCountNextlinkForSet(BeanPropertyDefinition property, Entity entity, JsonGenerator gen) throws IOException { + Object rawValue = property.getAccessor().getValue(entity); + if (rawValue == null) { + return; + } + EntitySet set = (EntitySet) rawValue; + long count = set.getCount(); + if (count >= 0) { + gen.writeNumberField(property.getName() + "@iot.count", count); + } + String nextLink = set.getNextLink(); + if (nextLink != null) { + gen.writeStringField(property.getName() + "@iot.nextLink", nextLink); + } + } + + private boolean serialiseNavigationElement(BeanPropertyDefinition property, Entity entity, boolean selected, JsonGenerator gen) throws IOException { + Object rawValue = property.getAccessor().getValue(entity); + if (rawValue == null) { + return selected; + } + NavigableElement value = (NavigableElement) rawValue; + // If navigation link set, and selected, output navigation link. + if (selected && value.getNavigationLink() != null && !value.getNavigationLink().isEmpty()) { + gen.writeFieldName(property.getName() + "@iot.navigationLink"); + gen.writeString(value.getNavigationLink()); + } + // If object should not be exported, skip any further processing. + return value.isExportObject(); + } + protected void serializeFieldCustomized( Entity entity, JsonGenerator gen, BeanPropertyDefinition property, List properties, - CustomSerialization annotation) throws Exception { + CustomSerialization annotation) throws IOException { // check if encoding field is present in current bean - // get calue + // get value // call CustomSerializationManager Optional encodingProperty = properties.stream().filter(p -> p.getName().equals(annotation.encoding())).findFirst(); if (!encodingProperty.isPresent()) { - // TODO use more specific exception type - throw new Exception("can not serialize instance of class '" + entity.getClass() + "'! \n" - + "Reason: trying to use custom serialization for field '" + property.getName() + "' but field '" + annotation.encoding() + "' specifying enconding is not present!"); + throw new JsonGenerationException("can not serialize instance of class '" + entity.getClass() + "'! \n" + + "Reason: trying to use custom serialization for field '" + property.getName() + "' but field '" + annotation.encoding() + "' specifying enconding is not present!", + gen); } Object value = encodingProperty.get().getAccessor().getValue(entity); String encodingType = null; @@ -188,6 +194,9 @@ protected void serializeFieldTyped( } } + JsonInclude.Value inclusion = beanPropertyDefinition.findInclusion(); + JsonInclude.Value defaultInclusion = serializers.getConfig().getDefaultPropertyInclusion(); + JsonInclude.Value usedInclusion = defaultInclusion.withOverrides(inclusion); BeanPropertyWriter bpw = new BeanPropertyWriter( beanPropertyDefinition, beanPropertyDefinition.getAccessor(), @@ -196,8 +205,11 @@ protected void serializeFieldTyped( null, // will be searched automatically typeSerializer, // will not be searched automatically beanPropertyDefinition.getAccessor().getType(), - suppressNulls(serializers.getConfig().getDefaultPropertyInclusion()), + suppressNulls(usedInclusion), suppressableValue(serializers.getConfig().getDefaultPropertyInclusion())); + if (!bpw.willSuppressNulls()) { + bpw.assignNullSerializer(NullSerializer.instance); + } bpw.serializeAsField(entity, gen, serializers); } catch (JsonMappingException ex) { Logger.getLogger(EntitySerializer.class.getName()).log(Level.SEVERE, null, ex); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetCamelCaseNamingStrategy.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetCamelCaseNamingStrategy.java similarity index 97% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetCamelCaseNamingStrategy.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetCamelCaseNamingStrategy.java index 0ce429539..2ab16f634 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetCamelCaseNamingStrategy.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetCamelCaseNamingStrategy.java @@ -15,7 +15,7 @@ * 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.sta.serialize; +package de.fraunhofer.iosb.ilt.sta.json.serialize; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.cfg.MapperConfig; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetResultSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetResultSerializer.java similarity index 88% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetResultSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetResultSerializer.java index a62a092ea..99658ed2e 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetResultSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetResultSerializer.java @@ -15,10 +15,9 @@ * 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.sta.serialize; +package de.fraunhofer.iosb.ilt.sta.json.serialize; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import de.fraunhofer.iosb.ilt.sta.model.ext.EntitySetResult; @@ -31,7 +30,7 @@ public class EntitySetResultSerializer extends JsonSerializer { @Override - public void serialize(EntitySetResult value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + public void serialize(EntitySetResult value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); long count = value.getValues().getCount(); if (count >= 0) { @@ -42,7 +41,6 @@ public void serialize(EntitySetResult value, JsonGenerator gen, SerializerProvid gen.writeStringField("@iot.nextLink", nextLink); } - // TODO begin/end array, iterate over content gen.writeFieldName("value"); gen.writeObject(value.getValues()); gen.writeEndObject(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetSerializer.java similarity index 79% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetSerializer.java index 311decb76..832f628f3 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntitySetSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/EntitySetSerializer.java @@ -15,26 +15,26 @@ * 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.sta.serialize; +package de.fraunhofer.iosb.ilt.sta.json.serialize; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import java.io.IOException; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import java.io.IOException; /** - * Defines how an EntitySet is serialized. If an EntitySet has a navigationLink property - EntityJsonSerializer makes sure that it is only serialized as a String, otherwise the EntitySet will be - serialized using this class and only the content of the EntitySet will be serialized. + * Defines how an EntitySet is serialized. If an EntitySet has a navigationLink + * property EntityJsonSerializer makes sure that it is only serialized as a + * String, otherwise the EntitySet will be serialized using this class and only + * the content of the EntitySet will be serialized. * * @author jab */ public class EntitySetSerializer extends JsonSerializer { @Override - public void serialize(EntitySet value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + public void serialize(EntitySet value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); gen.writeFieldName("value"); gen.writeObject(value.asList()); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/custom/geojson/GeoJsonSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/GeoJsonSerializer.java similarity index 67% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/custom/geojson/GeoJsonSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/GeoJsonSerializer.java index 21e2fb282..41fd5636f 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/custom/geojson/GeoJsonSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/GeoJsonSerializer.java @@ -15,16 +15,13 @@ * 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.sta.model.custom.geojson; +package de.fraunhofer.iosb.ilt.sta.json.serialize; +import de.fraunhofer.iosb.ilt.sta.model.mixin.FeatureMixIn; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import de.fraunhofer.iosb.ilt.sta.serialize.custom.CustomSerializer; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import de.fraunhofer.iosb.ilt.sta.json.serialize.custom.CustomSerializer; import org.geojson.Feature; import org.geojson.GeoJsonObject; @@ -34,10 +31,16 @@ */ public class GeoJsonSerializer implements CustomSerializer { - public static final Set encodings = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( - "application/geo+json", - "application/vnd.geo+json" - ))); + private static ObjectMapper mapper; + + private static ObjectMapper getMapper() { + if (mapper == null) { + mapper = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .addMixIn(Feature.class, FeatureMixIn.class); + } + return mapper; + } @Override public String serialize(Object object) throws JsonProcessingException { @@ -45,10 +48,7 @@ public String serialize(Object object) throws JsonProcessingException { return null; } GeoJsonObject geoJson = (GeoJsonObject) object; - ObjectMapper mapper = new ObjectMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - mapper.addMixIn(Feature.class, FeatureMixIn.class); - return mapper.writeValueAsString(geoJson); + return getMapper().writeValueAsString(geoJson); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/TimeValueSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/TimeValueSerializer.java similarity index 88% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/TimeValueSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/TimeValueSerializer.java index 590a4f05c..0f3264d8e 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/TimeValueSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/TimeValueSerializer.java @@ -15,10 +15,9 @@ * 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.sta.serialize; +package de.fraunhofer.iosb.ilt.sta.json.serialize; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; @@ -32,7 +31,7 @@ public class TimeValueSerializer extends JsonSerializer { @Override - public void serialize(TimeValue value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + public void serialize(TimeValue value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value.asISO8601() == null) { gen.writeNull(); } else { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerialization.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerialization.java similarity index 95% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerialization.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerialization.java index bb6d55e2d..cc42a2cc6 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerialization.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerialization.java @@ -15,7 +15,7 @@ * 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.sta.serialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.serialize.custom; import com.fasterxml.jackson.annotation.JacksonAnnotation; import java.lang.annotation.ElementType; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerializationManager.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerializationManager.java similarity index 95% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerializationManager.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerializationManager.java index 0436dd073..af43343c7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerializationManager.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerializationManager.java @@ -15,7 +15,7 @@ * 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.sta.serialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.serialize.custom; import java.util.HashMap; import java.util.Map; @@ -52,7 +52,6 @@ public CustomSerializer getSerializer(String encodingType) { result = customSerializers.get(encodingType); } if (result == null) { - // TODO what to create here??? result = new DefaultSerializer(); } return result; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerializer.java similarity index 94% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerializer.java index 9c08fb023..b1dc03065 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/CustomSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/CustomSerializer.java @@ -15,7 +15,7 @@ * 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.sta.serialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.serialize.custom; import java.io.IOException; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/DefaultSerializer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/DefaultSerializer.java similarity index 73% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/DefaultSerializer.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/DefaultSerializer.java index f52488c60..28065092d 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/custom/DefaultSerializer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/json/serialize/custom/DefaultSerializer.java @@ -15,9 +15,9 @@ * 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.sta.serialize.custom; +package de.fraunhofer.iosb.ilt.sta.json.serialize.custom; -import de.fraunhofer.iosb.ilt.sta.serialize.EntityFormatter; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; import java.io.IOException; /** @@ -26,21 +26,11 @@ */ class DefaultSerializer implements CustomSerializer { - private EntityFormatter formatter; - @Override public String serialize(Object object) throws IOException { if (object == null) { return null; } - return getFormatter().writeObject(object); + return EntityFormatter.writeObject(object); } - - public EntityFormatter getFormatter() { - if (formatter == null) { - formatter = new EntityFormatter(); - } - return formatter; - } - } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/EntityChangedMessage.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/EntityChangedMessage.java new file mode 100644 index 000000000..4ad38fbd0 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/EntityChangedMessage.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2018 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.sta.messagebus; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * + * @author scf + */ +public class EntityChangedMessage { + + /** + * The types of changes that can happen to entities. + */ + public enum Type { + CREATE, + UPDATE, + DELETE + } + /** + * The type of event that this message describes. + */ + private Type eventType; + /** + * The fields of the entity that were affected, if the type was UPDATE. For + * Create and Delete this is always empty, since all fields are affected. + */ + private Set epFields; + private Set npFields; + /** + * The type of the entity that was affected. + */ + private EntityType entityType; + /** + * The new version of the entity (for create/update) or the old entity (for + * delete). + */ + private Entity entity; + + public Type getEventType() { + return eventType; + } + + public EntityChangedMessage setEventType(Type eventType) { + this.eventType = eventType; + return this; + } + + public EntityType getEntityType() { + return entityType; + } + + public EntityChangedMessage setEntityType(EntityType entityType) { + this.entityType = entityType; + return this; + } + + public Set getEpFields() { + return epFields; + } + + public Set getNpFields() { + return npFields; + } + + @JsonIgnore + public Set getFields() { + if (epFields == null && npFields == null) { + return Collections.emptySet(); + } + Set fields = new HashSet<>(); + if (epFields != null) { + fields.addAll(epFields); + } + if (npFields != null) { + fields.addAll(npFields); + } + return fields; + } + + public EntityChangedMessage addField(Property field) { + if (field instanceof EntityProperty) { + addEpField((EntityProperty) field); + } else if (field instanceof NavigationProperty) { + addNpField((NavigationProperty) field); + } else { + throw new IllegalArgumentException("Field is not an entity or navigation property: " + field); + } + return this; + } + + public EntityChangedMessage addEpField(EntityProperty field) { + if (epFields == null) { + epFields = new HashSet<>(); + } + epFields.add(field); + return this; + } + + public EntityChangedMessage addNpField(NavigationProperty field) { + if (npFields == null) { + npFields = new HashSet<>(); + } + npFields.add(field); + return this; + } + + public Entity getEntity() { + return entity; + } + + public EntityChangedMessage setEntity(Entity entity) { + this.entity = entity; + this.entityType = entity.getEntityType(); + return this; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EntityChangedMessage other = (EntityChangedMessage) obj; + if (this.eventType != other.eventType) { + return false; + } + if (!Objects.equals(this.epFields, other.epFields)) { + return false; + } + if (this.entityType != other.entityType) { + return false; + } + return Objects.equals(this.entity, other.entity); + } + + @Override + public int hashCode() { + return Objects.hash(eventType, epFields, entityType, entity); + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/InternalMessageBus.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/InternalMessageBus.java new file mode 100644 index 000000000..7775f146a --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/InternalMessageBus.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 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.sta.messagebus; + +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.settings.BusSettings; +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.settings.Settings; +import de.fraunhofer.iosb.ilt.sta.util.ProcessorHelper; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A message bus implementation for in-JVM use. + * + * @author scf + */ +public class InternalMessageBus implements MessageBus { + + public static final String TAG_WORKER_COUNT = "workerPoolSize"; + public static final int DEFAULT_WORKER_COUNT = 2; + public static final String TAG_QUEUE_SIZE = "queueSize"; + public static final int DEFAULT_QUEUE_SIZE = 100; + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(InternalMessageBus.class); + + private BlockingQueue entityChangedMessageQueue; + private ExecutorService entityChangedExecutorService; + private final List listeners = new CopyOnWriteArrayList<>(); + + @Override + public void init(CoreSettings settings) { + BusSettings busSettings = settings.getBusSettings(); + Settings customSettings = busSettings.getCustomSettings(); + int poolSize = customSettings.getInt(TAG_WORKER_COUNT, DEFAULT_WORKER_COUNT); + int queueSize = customSettings.getInt(TAG_QUEUE_SIZE, DEFAULT_QUEUE_SIZE); + + entityChangedMessageQueue = new ArrayBlockingQueue<>(queueSize); + entityChangedExecutorService = ProcessorHelper.createProcessors( + poolSize, + entityChangedMessageQueue, + this::handleMessage, + "InternalBusProcessor"); + } + + @Override + public void stop() { + entityChangedExecutorService.shutdown(); + try { + if (entityChangedExecutorService.awaitTermination(2, TimeUnit.SECONDS)) { + return; + } + } catch (InterruptedException ex) { + LOGGER.error("Interrupted while waiting for shutdown.", ex); + Thread.currentThread().interrupt(); + } + List list = entityChangedExecutorService.shutdownNow(); + LOGGER.warn("There were {} messages left on the queue.", list.size()); + } + + @Override + public void sendMessage(EntityChangedMessage message) { + Entity entity = message.getEntity(); + EntityType entityType = entity.getEntityType(); + // We directly hand the entity on without serialization step. + // The receivers expect the navigation entities to not be exportable. + for (NavigationProperty property : entityType.getNavigationEntities()) { + Object parentObject = entity.getProperty(property); + if (parentObject instanceof Entity) { + Entity parentEntity = (Entity) parentObject; + parentEntity.setExportObject(false); + } + } + if (!entityChangedMessageQueue.offer(message)) { + LOGGER.error("Failed to add message to queue. Increase the queue size to allow a bigger buffer, or increase the worker pool size to empty the buffer quicker."); + } + } + + @Override + public void addMessageListener(MessageListener listener) { + listeners.add(listener); + } + + @Override + public void removeMessageListener(MessageListener listener) { + listeners.remove(listener); + } + + private void handleMessage(EntityChangedMessage message) { + for (MessageListener listener : listeners) { + try { + listener.messageReceived(message); + } catch (Exception ex) { + LOGGER.error("Listener threw exception on message reception.", ex); + } + } + } +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageBus.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageBus.java new file mode 100644 index 000000000..79b54fde7 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageBus.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.sta.messagebus; + +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; + +/** + * + * @author scf + */ +public interface MessageBus { + + public void init(CoreSettings settings); + + /** + * Stop the bus and clean up any worker threads. + */ + public void stop(); + + /** + * Offer an event message to be sent over the bus. This should return + * quickly. + * + * @param message the message to send. + */ + public void sendMessage(EntityChangedMessage message); + + public void addMessageListener(MessageListener listener); + + public void removeMessageListener(MessageListener listener); +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageBusFactory.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageBusFactory.java new file mode 100644 index 000000000..4a91ef48d --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageBusFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 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.sta.messagebus; + +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; + +/** + * + * @author scf + */ +public class MessageBusFactory { + + private static final String ERROR_MSG = "Could not generate MessageBus instance: "; + private static MessageBus instance; + + public static synchronized void init(CoreSettings settings) { + if (instance == null) { + if (settings == null) { + throw new IllegalArgumentException("settings must be non-null"); + } + try { + String mbClsName = settings.getBusSettings().getBusImplementationClass(); + Class messageBusClass = Class.forName(mbClsName); + instance = (MessageBus) messageBusClass.newInstance(); + instance.init(settings); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { + throw new IllegalArgumentException(ERROR_MSG + "Class '" + settings.getPersistenceSettings().getPersistenceManagerImplementationClass() + "' could not be found", ex); + } + } + } + + public static MessageBus getMessageBus() { + if (instance == null) { + throw new IllegalStateException("MessageBusFactory is not initialized! Call init() before accessing the instance."); + } + return instance; + } + + private MessageBusFactory() { + // should not be instantiated. + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageListener.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageListener.java new file mode 100644 index 000000000..a69cad8d0 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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.sta.messagebus; + +/** + * + * @author scf + */ +public interface MessageListener { + + /** + * Informs the listener that a message was received. Listeners should not do + * heavy work on the thread that called this method. + * + * @param message The message that was received. + */ + public void messageReceived(EntityChangedMessage message); +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MqttMessageBus.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MqttMessageBus.java new file mode 100644 index 000000000..c3a1e60ad --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/messagebus/MqttMessageBus.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2018 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.sta.messagebus; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; +import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; +import de.fraunhofer.iosb.ilt.sta.settings.BusSettings; +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.settings.Settings; +import de.fraunhofer.iosb.ilt.sta.util.ProcessorHelper; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A message bus implementation for in-JVM use. + * + * @author scf + */ +public class MqttMessageBus implements MessageBus, MqttCallback { + + public static final String TAG_SEND_WORKER_COUNT = "sendWorkerPoolSize"; + public static final int DEFAULT_SEND_WORKER_COUNT = 2; + public static final String TAG_RECV_WORKER_COUNT = "recvWorkerPoolSize"; + public static final int DEFAULT_RECV_WORKER_COUNT = 2; + public static final String TAG_SEND_QUEUE_SIZE = "sendQueueSize"; + public static final int DEFAULT_SEND_QUEUE_SIZE = 100; + public static final String TAG_RECV_QUEUE_SIZE = "recvQueueSize"; + public static final int DEFAULT_RECV_QUEUE_SIZE = 100; + public static final String TAG_MQTT_BROKER = "mqttBroker"; + public static final String TAG_TOPIC_NAME = "topicName"; + public static final String DEFAULT_TOPIC_NAME = "FROST-Bus"; + public static final String TAG_QOS_LEVEL = "qosLevel"; + public static final int DEFAULT_QOS_LEVEL = 2; + public static final String TAG_MAX_IN_FLIGHT = "maxInFlight"; + public static final int DEFAULT_MAX_IN_FLIGHT = 50; + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(MqttMessageBus.class); + + private int sendPoolSize; + private int sendQueueSize; + private int recvPoolSize; + private int recvQueueSize; + private BlockingQueue sendQueue; + private ExecutorService sendService; + private BlockingQueue recvQueue; + private ExecutorService recvService; + private final List listeners = new CopyOnWriteArrayList<>(); + + private String broker; + private String clientId = "FROST-MQTT-Bus-" + UUID.randomUUID(); + private MqttClient client; + private String topicName; + private int qosLevel; + private int maxInFlight; + private boolean listening = false; + + private ObjectMapper formatter; + private EntityParser parser; + + @Override + public void init(CoreSettings settings) { + BusSettings busSettings = settings.getBusSettings(); + Settings customSettings = busSettings.getCustomSettings(); + sendPoolSize = customSettings.getInt(TAG_SEND_WORKER_COUNT, DEFAULT_SEND_WORKER_COUNT); + sendQueueSize = customSettings.getInt(TAG_SEND_QUEUE_SIZE, DEFAULT_SEND_QUEUE_SIZE); + recvPoolSize = customSettings.getInt(TAG_RECV_WORKER_COUNT, DEFAULT_RECV_WORKER_COUNT); + recvQueueSize = customSettings.getInt(TAG_RECV_QUEUE_SIZE, DEFAULT_RECV_QUEUE_SIZE); + + sendQueue = new ArrayBlockingQueue<>(sendQueueSize); + sendService = ProcessorHelper.createProcessors( + sendPoolSize, + sendQueue, + this::handleMessageSent, + "mqttBusSend"); + + recvQueue = new ArrayBlockingQueue<>(recvQueueSize); + recvService = ProcessorHelper.createProcessors( + recvPoolSize, + recvQueue, + this::handleMessageReceived, + "mqttBusRecv"); + + broker = customSettings.get(TAG_MQTT_BROKER); + if (broker == null || broker.isEmpty()) { + LOGGER.error("Broker url should be configured in option bus.{}", TAG_MQTT_BROKER); + } + topicName = customSettings.getWithDefault(TAG_TOPIC_NAME, DEFAULT_TOPIC_NAME, String.class); + qosLevel = customSettings.getInt(TAG_QOS_LEVEL, DEFAULT_QOS_LEVEL); + maxInFlight = customSettings.getInt(TAG_MAX_IN_FLIGHT, DEFAULT_MAX_IN_FLIGHT); + connect(); + + formatter = EntityFormatter.getObjectMapper(); + parser = new EntityParser(PersistenceManagerFactory.getInstance().create().getIdManager().getIdClass()); + } + + private synchronized void connect() { + if (client == null) { + try { + client = new MqttClient(broker, clientId, new MemoryPersistence()); + client.setCallback(this); + } catch (MqttException ex) { + LOGGER.error("Failed to connect to broker: {}", broker); + LOGGER.error("", ex); + return; + } + } + if (!client.isConnected()) { + try { + LOGGER.info("paho-client connecting to broker: {}", broker); + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setAutomaticReconnect(true); + connOpts.setCleanSession(false); + connOpts.setKeepAliveInterval(30); + connOpts.setConnectionTimeout(30); + connOpts.setMaxInflight(maxInFlight); + client.connect(connOpts); + LOGGER.info("paho-client connected to broker"); + } catch (MqttException ex) { + LOGGER.error("Failed to connect to broker: {}", broker); + LOGGER.error("", ex); + } + } + + } + + private synchronized void disconnect() { + if (client == null) { + return; + } + if (client.isConnected()) { + try { + LOGGER.info("paho-client disconnecting from broker: {}", broker); + client.disconnect(1000); + } catch (MqttException ex) { + LOGGER.error("Exception disconnecting client.", ex); + } + } + try { + LOGGER.info("paho-client closing"); + client.close(); + } catch (MqttException ex) { + LOGGER.error("Exception closing client.", ex); + } + client = null; + } + + private synchronized void startListening() { + try { + LOGGER.info("paho-client subscribing to topic: {}", topicName); + if (!client.isConnected()) { + connect(); + } + client.subscribe(topicName, qosLevel); + listening = true; + } catch (MqttException ex) { + LOGGER.error("Failed to start listening.", ex); + } + } + + private synchronized void stopListening() { + if (!listening) { + return; + } + try { + LOGGER.info("paho-client unsubscribing from topic: {}", topicName); + client.unsubscribe(topicName); + listening = false; + } catch (MqttException ex) { + LOGGER.error("Failed to stop listening.", ex); + } + } + + @Override + public void stop() { + LOGGER.info("Message bus shutting down."); + stopListening(); + disconnect(); + ProcessorHelper.shutdownProcessors(sendService, sendQueue, 10, TimeUnit.SECONDS); + ProcessorHelper.shutdownProcessors(recvService, recvQueue, 10, TimeUnit.SECONDS); + LOGGER.info("Message bus closed."); + } + + @Override + public void sendMessage(EntityChangedMessage message) { + if (!sendQueue.offer(message)) { + LOGGER.error("Failed to add message to send-queue. Increase {} (currently {}) to allow a bigger buffer, or increase {} (currently {}) to empty the buffer quicker.", + TAG_SEND_QUEUE_SIZE, sendQueueSize, TAG_SEND_WORKER_COUNT, sendPoolSize); + } + } + + @Override + public synchronized void addMessageListener(MessageListener listener) { + listeners.add(listener); + if (!listening) { + startListening(); + } + } + + @Override + public synchronized void removeMessageListener(MessageListener listener) { + listeners.remove(listener); + if (listeners.isEmpty()) { + stopListening(); + } + } + + private void handleMessageSent(EntityChangedMessage message) { + try { + String serialisedMessage = formatter.writeValueAsString(message); + byte[] bytes = serialisedMessage.getBytes(StringHelper.ENCODING); + if (!client.isConnected()) { + connect(); + } + client.publish(topicName, bytes, qosLevel, false); + } catch (MqttException | JsonProcessingException ex) { + LOGGER.error("Failed to publish message to bus.", ex); + } + } + + @Override + public void connectionLost(Throwable cause) { + LOGGER.warn("Connection to message bus lost."); + LOGGER.debug("", cause); + } + + @Override + public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { + String serialisedEcMessage = new String(mqttMessage.getPayload(), StringHelper.ENCODING); + EntityChangedMessage ecMessage = parser.parseObject(EntityChangedMessage.class, serialisedEcMessage); + if (!recvQueue.offer(ecMessage)) { + LOGGER.error("Failed to add message to receive-queue. Increase {} (currently {}) to allow a bigger buffer, or increase {} (currently {}) to empty the buffer quicker.", + TAG_RECV_QUEUE_SIZE, recvQueueSize, TAG_RECV_WORKER_COUNT, recvPoolSize); + } + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + // Nothing to do... + } + + private void handleMessageReceived(EntityChangedMessage message) { + for (MessageListener listener : listeners) { + try { + listener.messageReceived(message); + } catch (Exception ex) { + LOGGER.error("Listener threw exception on message reception.", ex); + } + } + } +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/AbstractDatastream.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/AbstractDatastream.java new file mode 100644 index 000000000..041cadd3e --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/AbstractDatastream.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2016 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.sta.model; + +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.NamedEntity; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import java.util.Objects; +import org.geojson.Polygon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author jab, scf + */ +public abstract class AbstractDatastream extends NamedEntity { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDatastream.class); + private String observationType; + private Polygon observedArea; // reference to GeoJSON library + private TimeInterval phenomenonTime; + private TimeInterval resultTime; + private Sensor sensor; + private Thing thing; + private EntitySet observations; + + private boolean setObservationType; + private boolean setObservedArea; + private boolean setPhenomenonTime; + private boolean setResultTime; + private boolean setSensor; + private boolean setThing; + + public AbstractDatastream() { + this(null); + } + + public AbstractDatastream(Id id) { + super(id); + this.observations = new EntitySetImpl<>(EntityType.OBSERVATION); + } + + @Override + public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException { + ResourcePathElement parent = containingSet.getParent(); + if (parent instanceof EntityPathElement) { + EntityPathElement parentEntity = (EntityPathElement) parent; + Id parentId = parentEntity.getId(); + if (parentId != null) { + checkParent(parentEntity, parentId); + } + } + + super.complete(containingSet); + } + + protected boolean checkParent(EntityPathElement parentEntity, Id parentId) { + switch (parentEntity.getEntityType()) { + case SENSOR: + setSensor(new Sensor(parentId)); + LOGGER.debug("Set sensorId to {}.", parentId); + return true; + + case THING: + setThing(new Thing(parentId)); + LOGGER.debug("Set thingId to {}.", parentId); + return true; + + default: + LOGGER.error("Incorrect 'parent' entity type for {}: {}", getEntityType(), parentEntity.getEntityType()); + return false; + } + } + + @Override + public void setEntityPropertiesSet() { + super.setEntityPropertiesSet(); + setObservationType = true; + setObservedArea = true; + setPhenomenonTime = true; + setResultTime = true; + } + + public String getObservationType() { + return observationType; + } + + /** + * Set the observation type without changing the "set" status of it. + * + * @param observationType The observation type to set. + */ + protected final void setObservationTypeIntern(String observationType) { + this.observationType = observationType; + } + + public void setObservationType(String observationType) { + this.observationType = observationType; + setObservationType = observationType != null; + } + + /** + * Flag indicating the observation type was set by the user. + * + * @return Flag indicating the observation type was set by the user. + */ + public boolean isSetObservationType() { + return setObservationType; + } + + public Polygon getObservedArea() { + return observedArea; + } + + public void setObservedArea(Polygon observedArea) { + this.observedArea = observedArea; + setObservedArea = true; + } + + /** + * Flag indicating the observedArea was set by the user. + * + * @return Flag indicating the observedArea was set by the user. + */ + public boolean isSetObservedArea() { + return setObservedArea; + } + + public TimeInterval getPhenomenonTime() { + return phenomenonTime; + } + + public void setPhenomenonTime(TimeInterval phenomenonTime) { + this.phenomenonTime = phenomenonTime; + setPhenomenonTime = true; + } + + /** + * Flag indicating the PhenomenonTime was set by the user. + * + * @return Flag indicating the PhenomenonTime was set by the user. + */ + public boolean isSetPhenomenonTime() { + return setPhenomenonTime; + } + + public TimeInterval getResultTime() { + return resultTime; + } + + public void setResultTime(TimeInterval resultTime) { + this.resultTime = resultTime; + setResultTime = true; + } + + /** + * Flag indicating the ResultTime was set by the user. + * + * @return Flag indicating the ResultTime was set by the user. + */ + public boolean isSetResultTime() { + return setResultTime; + } + + public Sensor getSensor() { + return sensor; + } + + public void setSensor(Sensor sensor) { + this.sensor = sensor; + setSensor = sensor != null; + } + + /** + * Flag indicating the Sensor was set by the user. + * + * @return Flag indicating the Sensor was set by the user. + */ + public boolean isSetSensor() { + return setSensor; + } + + public Thing getThing() { + return thing; + } + + public void setThing(Thing thing) { + this.thing = thing; + setThing = thing != null; + } + + /** + * Flag indicating the Thing was set by the user. + * + * @return Flag indicating the Thing was set by the user. + */ + public boolean isSetThing() { + return setThing; + } + + public EntitySet getObservations() { + return observations; + } + + public void setObservations(EntitySet observations) { + this.observations = observations; + } + + @Override + public int hashCode() { + return Objects.hash( + super.hashCode(), + observationType, + observedArea, + phenomenonTime, + resultTime, + sensor, + thing, + observations); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AbstractDatastream other = (AbstractDatastream) obj; + return super.equals(other) + && Objects.equals(observationType, other.observationType) + && Objects.equals(observedArea, other.observedArea) + && Objects.equals(phenomenonTime, other.phenomenonTime) + && Objects.equals(resultTime, other.resultTime) + && Objects.equals(sensor, other.sensor) + && Objects.equals(thing, other.thing) + && Objects.equals(observations, other.observations); + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Datastream.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Datastream.java index 0b2f1aca4..5a1a298dc 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Datastream.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Datastream.java @@ -17,304 +17,99 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.builder.ObservedPropertyBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.SensorBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; -import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; -import org.geojson.Polygon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * - * @author jab + * @author jab, scf */ -public class Datastream extends AbstractEntity { +public class Datastream extends AbstractDatastream { /** * The logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(Datastream.class); - private String name; - private String description; - private String observationType; - private Map properties; private UnitOfMeasurement unitOfMeasurement; - private Polygon observedArea; // reference to GeoJSON library - private TimeInterval phenomenonTime; - private TimeInterval resultTime; - private Sensor sensor; private ObservedProperty observedProperty; - private Thing thing; - private EntitySet observations; - private boolean setName; - private boolean setDescription; - private boolean setObservationType; private boolean setUnitOfMeasurement; - private boolean setObservedArea; - private boolean setPhenomenonTime; - private boolean setResultTime; - private boolean setSensor; private boolean setObservedProperty; - private boolean setThing; - private boolean setProperties; public Datastream() { - this.observations = new EntitySetImpl<>(EntityType.Observation); - this.unitOfMeasurement = new UnitOfMeasurement(); + this(true, null); } - public Datastream(Id id, - String selfLink, - String navigationLink, - String name, - String description, - String observationType, - Map properties, - UnitOfMeasurement unitOfMeasurement, - Polygon observedArea, - TimeInterval phenomenonTime, - TimeInterval resultTime, - Sensor sensor, - ObservedProperty observedProperty, - Thing thing, - EntitySet observations) { - super(id, selfLink, navigationLink); - this.name = name; - this.description = description; - this.observationType = observationType; - this.unitOfMeasurement = unitOfMeasurement; - this.observedArea = observedArea; - this.phenomenonTime = phenomenonTime; - this.resultTime = resultTime; - this.sensor = sensor; - this.observedProperty = observedProperty; - this.thing = thing; - this.observations = observations; - if (properties != null && !properties.isEmpty()) { - this.properties = new HashMap<>(properties); + public Datastream(Id id) { + this(true, id); + } + + public Datastream(boolean onlyId, Id id) { + super(id); + if (!onlyId) { + this.unitOfMeasurement = new UnitOfMeasurement(); } } @Override public EntityType getEntityType() { - return EntityType.Datastream; + return EntityType.DATASTREAM; } @Override - public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException { - ResourcePathElement parent = containingSet.getParent(); - if (parent != null && parent instanceof EntityPathElement) { - EntityPathElement parentEntity = (EntityPathElement) parent; - Id parentId = parentEntity.getId(); - if (parentId != null) { - switch (parentEntity.getEntityType()) { - case ObservedProperty: - setObservedProperty(new ObservedPropertyBuilder().setId(parentId).build()); - LOGGER.debug("Set observedPropertyId to {}.", parentId); - break; - - case Sensor: - setSensor(new SensorBuilder().setId(parentId).build()); - LOGGER.debug("Set sensorId to {}.", parentId); - break; - - case Thing: - setThing(new ThingBuilder().setId(parentId).build()); - LOGGER.debug("Set thingId to {}.", parentId); - break; - } - } + protected boolean checkParent(EntityPathElement parentEntity, Id parentId) { + if (parentEntity.getEntityType() == EntityType.OBSERVEDPROPERTY) { + setObservedProperty(new ObservedProperty(parentId)); + LOGGER.debug("Set observedPropertyId to {}.", parentId); + return true; } - super.complete(containingSet); + return super.checkParent(parentEntity, parentId); } @Override public void setEntityPropertiesSet() { - setDescription = true; - setObservationType = true; + super.setEntityPropertiesSet(); setUnitOfMeasurement = true; - setProperties = true; - } - - public String getName() { - return name; - } - - /** - * @return the description - */ - public String getDescription() { - return description; - } - - /** - * @return the observationType - */ - public String getObservationType() { - return observationType; } - public Map getProperties() { - return properties; - } - - /** - * @return the unitOfMeasurement - */ public UnitOfMeasurement getUnitOfMeasurement() { return unitOfMeasurement; } - /** - * @return the observedArea - */ - public Polygon getObservedArea() { - return observedArea; - } - - /** - * @return the phenomenonTime - */ - public TimeInterval getPhenomenonTime() { - return phenomenonTime; - } - - /** - * @return the resultTime - */ - public TimeInterval getResultTime() { - return resultTime; - } - - /** - * @return the sensor - */ - public Sensor getSensor() { - return sensor; - } - - /** - * @param observedArea the observedArea to set - */ - public void setObservedArea(Polygon observedArea) { - this.observedArea = observedArea; - setObservedArea = true; - } - - /** - * @param phenomenonTime the phenomenonTime to set - */ - public void setPhenomenonTime(TimeInterval phenomenonTime) { - this.phenomenonTime = phenomenonTime; - setPhenomenonTime = true; + public void setUnitOfMeasurement(UnitOfMeasurement unitOfMeasurement) { + this.unitOfMeasurement = unitOfMeasurement; + setUnitOfMeasurement = unitOfMeasurement != null; } - /** - * @param resultTime the resultTime to set - */ - public void setResultTime(TimeInterval resultTime) { - this.resultTime = resultTime; - setResultTime = true; + public boolean isSetUnitOfMeasurement() { + return setUnitOfMeasurement; } - /** - * @return the observedProperty - */ public ObservedProperty getObservedProperty() { return observedProperty; } - public void setName(String name) { - this.name = name; - setName = true; - } - - /** - * @param description the description to set - */ - public void setDescription(String description) { - this.description = description; - setDescription = true; - } - - /** - * @param observationType the observationType to set - */ - public void setObservationType(String observationType) { - this.observationType = observationType; - setObservationType = true; - } - - /** - * @param unitOfMeasurement the unitOfMeasurement to set - */ - public void setUnitOfMeasurement(UnitOfMeasurement unitOfMeasurement) { - this.unitOfMeasurement = unitOfMeasurement; - setUnitOfMeasurement = true; - } - - /** - * @param sensor the sensor to set - */ - public void setSensor(Sensor sensor) { - this.sensor = sensor; - setSensor = true; - } - - /** - * @param observedProperty the observedProperty to set - */ public void setObservedProperty(ObservedProperty observedProperty) { this.observedProperty = observedProperty; - setObservedProperty = true; + setObservedProperty = observedProperty != null; } /** - * @return the thing + * @return true if the ObservedProperty was explicitly set. */ - public Thing getThing() { - return thing; - } - - /** - * @param thing the thing to set - */ - public void setThing(Thing thing) { - this.thing = thing; - setThing = true; + public boolean isSetObservedProperty() { + return setObservedProperty; } @Override public int hashCode() { - int hash = 5; - hash = 29 * hash + Objects.hashCode(this.name); - hash = 29 * hash + Objects.hashCode(this.description); - hash = 29 * hash + Objects.hashCode(this.observationType); - hash = 29 * hash + Objects.hashCode(this.unitOfMeasurement); - hash = 29 * hash + Objects.hashCode(this.observedArea); - hash = 29 * hash + Objects.hashCode(this.phenomenonTime); - hash = 29 * hash + Objects.hashCode(this.resultTime); - hash = 29 * hash + Objects.hashCode(this.sensor); - hash = 29 * hash + Objects.hashCode(this.observedProperty); - hash = 29 * hash + Objects.hashCode(this.properties); - hash = 29 * hash + Objects.hashCode(this.thing); - return hash; + return Objects.hash(super.hashCode(), unitOfMeasurement, observedProperty); } @Override @@ -329,133 +124,9 @@ public boolean equals(Object obj) { return false; } final Datastream other = (Datastream) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.description, other.description)) { - return false; - } - if (!Objects.equals(this.observationType, other.observationType)) { - return false; - } - if (!Objects.equals(this.observedArea, other.observedArea)) { - return false; - } - if (!Objects.equals(this.phenomenonTime, other.phenomenonTime)) { - return false; - } - if (!Objects.equals(this.resultTime, other.resultTime)) { - return false; - } - if (!Objects.equals(this.sensor, other.sensor)) { - return false; - } - if (!Objects.equals(this.observedProperty, other.observedProperty)) { - return false; - } - if (!Objects.equals(this.thing, other.thing)) { - return false; - } - if (!Objects.equals(this.properties, other.properties)) { - return false; - } - return true; - } - - /** - * @return the observations - */ - public EntitySet getObservations() { - return observations; - } - - /** - * @param observations the observations to set - */ - public void setObservations(EntitySet observations) { - this.observations = observations; - } - - public void setProperties(Map properties) { - if (properties != null && properties.isEmpty()) { - properties = null; - } - this.properties = properties; - setProperties = true; - } - - public boolean isSetName() { - return setName; - } - - /** - * @return the setDescription - */ - public boolean isSetDescription() { - return setDescription; - } - - /** - * @return the setObservationType - */ - public boolean isSetObservationType() { - return setObservationType; - } - - /** - * @return the setObservedArea - */ - public boolean isSetObservedArea() { - return setObservedArea; - } - - /** - * @return the setPhenomenonTime - */ - public boolean isSetPhenomenonTime() { - return setPhenomenonTime; - } - - /** - * @return the setResultTime - */ - public boolean isSetResultTime() { - return setResultTime; - } - - /** - * @return the setSensor - */ - public boolean isSetSensor() { - return setSensor; - } - - /** - * @return the setObservedProperty - */ - public boolean isSetObservedProperty() { - return setObservedProperty; - } - - public boolean isSetProperties() { - return setProperties; - } - - /** - * @return the setThing - */ - public boolean isSetThing() { - return setThing; - } - - /** - * @return the setUnitOfMeasurement - */ - public boolean isSetUnitOfMeasurement() { - return setUnitOfMeasurement; + return super.equals(other) + && Objects.equals(observedProperty, other.observedProperty) + && Objects.equals(unitOfMeasurement, other.unitOfMeasurement); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/FeatureOfInterest.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/FeatureOfInterest.java index 24e16b8fa..5043d9743 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/FeatureOfInterest.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/FeatureOfInterest.java @@ -17,158 +17,84 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.NamedEntity; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; /** * - * @author jab + * @author jab, scf */ -public class FeatureOfInterest extends AbstractEntity { +public class FeatureOfInterest extends NamedEntity { - private String name; - private String description; private String encodingType; private Object feature; - private Map properties; private EntitySet observations; - private boolean setName; - private boolean setDescription; private boolean setEncodingType; private boolean setFeature; - private boolean setProperties; public FeatureOfInterest() { - this.observations = new EntitySetImpl<>(EntityType.Observation); + this(null); } - public FeatureOfInterest( - Id id, - String selfLink, - String navigationLink, - String name, - String description, - String encodingType, - Object feature, - Map properties, - EntitySet observations) { - super(id, selfLink, navigationLink); - this.name = name; - this.description = description; - this.encodingType = encodingType; - this.feature = feature; - this.observations = observations; - if (properties != null && !properties.isEmpty()) { - this.properties = new HashMap<>(properties); - } + public FeatureOfInterest(Id id) { + super(id); + this.observations = new EntitySetImpl<>(EntityType.OBSERVATION); } @Override public EntityType getEntityType() { - return EntityType.FeatureOfInterest; + return EntityType.FEATUREOFINTEREST; } @Override public void setEntityPropertiesSet() { - setDescription = true; + super.setEntityPropertiesSet(); setEncodingType = true; setFeature = true; - setProperties = true; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; } public String getEncodingType() { return encodingType; } - public Object getFeature() { - return feature; - } - - public Map getProperties() { - return properties; - } - - public EntitySet getObservations() { - return observations; - } - - public boolean isSetName() { - return setName; - } - - public boolean isSetDescription() { - return setDescription; + public void setEncodingType(String encodingType) { + this.encodingType = encodingType; + setEncodingType = encodingType != null; } public boolean isSetEncodingType() { return setEncodingType; } - public boolean isSetFeature() { - return setFeature; - } - - public boolean isSetProperties() { - return setProperties; - } - - public void setName(String name) { - this.name = name; - setName = true; + public Object getFeature() { + return feature; } - public void setDescription(String description) { - this.description = description; - setDescription = true; + public void setFeature(Object feature) { + setFeature = feature != null; + this.feature = feature; } - public void setEncodingType(String encodingType) { - this.encodingType = encodingType; - setEncodingType = true; + public boolean isSetFeature() { + return setFeature; } - public void setFeature(Object feature) { - setFeature = true; - this.feature = feature; + public EntitySet getObservations() { + return observations; } public void setObservations(EntitySet observations) { this.observations = observations; } - public void setProperties(Map properties) { - if (properties != null && properties.isEmpty()) { - properties = null; - } - this.properties = properties; - setProperties = true; - } - @Override public int hashCode() { - int hash = 7; - hash = 41 * hash + Objects.hashCode(this.name); - hash = 41 * hash + Objects.hashCode(this.description); - hash = 41 * hash + Objects.hashCode(this.encodingType); - hash = 41 * hash + Objects.hashCode(this.feature); - hash = 41 * hash + Objects.hashCode(this.observations); - hash = 41 * hash + Objects.hashCode(this.properties); - return hash; + return Objects.hash(super.hashCode(), encodingType, feature, observations); } @Override @@ -183,28 +109,10 @@ public boolean equals(Object obj) { return false; } final FeatureOfInterest other = (FeatureOfInterest) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.description, other.description)) { - return false; - } - if (!Objects.equals(this.encodingType, other.encodingType)) { - return false; - } - if (!Objects.equals(this.feature, other.feature)) { - return false; - } - if (!Objects.equals(this.observations, other.observations)) { - return false; - } - if (!Objects.equals(this.properties, other.properties)) { - return false; - } - return true; + return super.equals(other) + && Objects.equals(encodingType, other.encodingType) + && Objects.equals(feature, other.feature) + && Objects.equals(observations, other.observations); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/HistoricalLocation.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/HistoricalLocation.java index 99198c606..d05b355ab 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/HistoricalLocation.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/HistoricalLocation.java @@ -17,12 +17,11 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; @@ -34,7 +33,7 @@ /** * - * @author jab + * @author jab, scf */ public class HistoricalLocation extends AbstractEntity { @@ -50,25 +49,17 @@ public class HistoricalLocation extends AbstractEntity { private boolean setThing; public HistoricalLocation() { - this.locations = new EntitySetImpl<>(EntityType.Location); + this(null); } - public HistoricalLocation( - Id id, - String selfLink, - String navigationLink, - TimeInstant time, - Thing thing, - EntitySet locations) { - super(id, selfLink, navigationLink); - this.time = time; - this.thing = thing; - this.locations = locations; + public HistoricalLocation(Id id) { + super(id); + this.locations = new EntitySetImpl<>(EntityType.LOCATION); } @Override public EntityType getEntityType() { - return EntityType.HistoricalLocation; + return EntityType.HISTORICALLOCATION; } @Override @@ -78,15 +69,15 @@ public void complete(EntitySetPathElement containingSet) throws IncompleteEntity throw new IllegalStateException("Set of type " + type + " can not contain a " + getEntityType()); } ResourcePathElement parent = containingSet.getParent(); - if (parent != null && parent instanceof EntityPathElement) { + if (parent instanceof EntityPathElement) { EntityPathElement parentEntity = (EntityPathElement) parent; Id parentId = parentEntity.getId(); if (parentId != null) { - switch (parentEntity.getEntityType()) { - case Thing: - setThing(new ThingBuilder().setId(parentId).build()); - LOGGER.debug("Set thingId to {}.", parentId); - break; + if (parentEntity.getEntityType() == EntityType.THING) { + setThing(new Thing(parentId)); + LOGGER.debug("Set thingId to {}.", parentId); + } else { + LOGGER.error("Incorrect 'parent' entity type for {}: {}", getEntityType(), parentEntity.getEntityType()); } } } @@ -105,30 +96,30 @@ public TimeInstant getTime() { return time; } - public Thing getThing() { - return thing; - } - - public EntitySet getLocations() { - return locations; + public void setTime(TimeInstant time) { + this.time = time; + setTime = time != null; } public boolean isSetTime() { return setTime; } - public boolean isSetThing() { - return setThing; - } - - public void setTime(TimeInstant time) { - this.time = time; - setTime = true; + public Thing getThing() { + return thing; } public void setThing(Thing thing) { this.thing = thing; - setThing = true; + setThing = thing != null; + } + + public boolean isSetThing() { + return setThing; + } + + public EntitySet getLocations() { + return locations; } public void setLocations(EntitySet locations) { @@ -137,11 +128,7 @@ public void setLocations(EntitySet locations) { @Override public int hashCode() { - int hash = 5; - hash = 97 * hash + Objects.hashCode(this.time); - hash = 97 * hash + Objects.hashCode(this.thing); - hash = 97 * hash + Objects.hashCode(this.locations); - return hash; + return Objects.hash(time, thing, locations); } @Override @@ -156,18 +143,10 @@ public boolean equals(Object obj) { return false; } final HistoricalLocation other = (HistoricalLocation) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.time, other.time)) { - return false; - } - if (!Objects.equals(this.thing, other.thing)) { - return false; - } - if (!Objects.equals(this.locations, other.locations)) { - return false; - } - return true; + return super.equals(other) + && Objects.equals(this.time, other.time) + && Objects.equals(this.thing, other.thing) + && Objects.equals(this.locations, other.locations); } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Location.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Location.java index 421f05a2b..83e1485a5 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Location.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Location.java @@ -17,91 +17,64 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; -import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.NamedEntity; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * - * @author jab + * @author jab, scf */ -public class Location extends AbstractEntity { +public class Location extends NamedEntity { /** * The logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(Location.class); - private String name; - private String description; private String encodingType; private Object location; - private Map properties; private EntitySet historicalLocations; // 0..* private EntitySet things; - private boolean setName; - private boolean setDescription; private boolean setEncodingType; private boolean setLocation; - private boolean setProperties; public Location() { - this.things = new EntitySetImpl<>(EntityType.Thing); - this.historicalLocations = new EntitySetImpl<>(EntityType.HistoricalLocation); - } - - public Location( - Id id, - String selfLink, - String navigationLink, - String name, - String description, - String encodingType, - Object location, - Map properties, - EntitySet historicalLocations, - EntitySet things) { - super(id, selfLink, navigationLink); - this.name = name; - this.description = description; - this.encodingType = encodingType; - this.location = location; - this.historicalLocations = historicalLocations; - this.things = things; - if (properties != null && !properties.isEmpty()) { - this.properties = new HashMap<>(properties); - } + this(null); + } + + public Location(Id id) { + super(id); + this.things = new EntitySetImpl<>(EntityType.THING); + this.historicalLocations = new EntitySetImpl<>(EntityType.HISTORICALLOCATION); } @Override public EntityType getEntityType() { - return EntityType.Location; + return EntityType.LOCATION; } @Override public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException { ResourcePathElement parent = containingSet.getParent(); - if (parent != null && parent instanceof EntityPathElement) { + if (parent instanceof EntityPathElement) { EntityPathElement parentEntity = (EntityPathElement) parent; Id parentId = parentEntity.getId(); if (parentId != null) { - switch (parentEntity.getEntityType()) { - case Thing: - getThings().add(new ThingBuilder().setId(parentId).build()); - LOGGER.debug("Added thingId to {}.", parentId); - break; + if (parentEntity.getEntityType() == EntityType.THING) { + getThings().add(new Thing(parentId)); + LOGGER.debug("Added thingId to {}.", parentId); + } else { + LOGGER.error("Incorrect 'parent' entity type for {}: {}", getEntityType(), parentEntity.getEntityType()); } } } @@ -110,107 +83,56 @@ public void complete(EntitySetPathElement containingSet) throws IncompleteEntity @Override public void setEntityPropertiesSet() { - setDescription = true; + super.setEntityPropertiesSet(); setEncodingType = true; setLocation = true; - setProperties = true; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; } public String getEncodingType() { return encodingType; } - public Object getLocation() { - return location; - } - - public Map getProperties() { - return properties; - } - - public EntitySet getHistoricalLocations() { - return historicalLocations; - } - - public EntitySet getThings() { - return things; - } - - public boolean isSetName() { - return setName; - } - - public boolean isSetDescription() { - return setDescription; + public void setEncodingType(String encodingType) { + this.encodingType = encodingType; + setEncodingType = encodingType != null; } public boolean isSetEncodingType() { return setEncodingType; } - public boolean isSetLocation() { - return setLocation; - } - - public boolean isSetProperties() { - return setProperties; - } - - public void setName(String name) { - this.name = name; - setName = true; + public Object getLocation() { + return location; } - public void setDescription(String description) { - this.description = description; - setDescription = true; + public void setLocation(Object location) { + this.location = location; + setLocation = location != null; } - public void setEncodingType(String encodingType) { - this.encodingType = encodingType; - setEncodingType = true; + public boolean isSetLocation() { + return setLocation; } - public void setLocation(Object location) { - this.location = location; - setLocation = true; + public EntitySet getHistoricalLocations() { + return historicalLocations; } public void setHistoricalLocations(EntitySet historicalLocations) { this.historicalLocations = historicalLocations; } - public void setThings(EntitySet things) { - this.things = things; + public EntitySet getThings() { + return things; } - public void setProperties(Map properties) { - if (properties != null && properties.isEmpty()) { - properties = null; - } - this.properties = properties; - setProperties = true; + public void setThings(EntitySet things) { + this.things = things; } @Override public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.name); - hash = 53 * hash + Objects.hashCode(this.description); - hash = 53 * hash + Objects.hashCode(this.encodingType); - hash = 53 * hash + Objects.hashCode(this.location); - hash = 53 * hash + Objects.hashCode(this.historicalLocations); - hash = 53 * hash + Objects.hashCode(this.properties); - hash = 53 * hash + Objects.hashCode(this.things); - return hash; + return Objects.hash(super.hashCode(), encodingType, location, historicalLocations, things); } @Override @@ -225,30 +147,11 @@ public boolean equals(Object obj) { return false; } final Location other = (Location) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.description, other.description)) { - return false; - } - if (!Objects.equals(this.encodingType, other.encodingType)) { - return false; - } - if (!Objects.equals(this.location, other.location)) { - return false; - } - if (!Objects.equals(this.historicalLocations, other.historicalLocations)) { - return false; - } - if (!Objects.equals(this.things, other.things)) { - return false; - } - if (!Objects.equals(this.properties, other.properties)) { - return false; - } - return true; + return super.equals(other) + && Objects.equals(encodingType, other.encodingType) + && Objects.equals(location, other.location) + && Objects.equals(historicalLocations, other.historicalLocations) + && Objects.equals(things, other.things); } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/MultiDatastream.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/MultiDatastream.java index 698f2ac58..53e8890a1 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/MultiDatastream.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/MultiDatastream.java @@ -17,130 +17,46 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.builder.SensorBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; -import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.ext.ObservationType; import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; -import org.geojson.Polygon; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * - * @author jab + * @author jab, scf */ -public class MultiDatastream extends AbstractEntity { +public class MultiDatastream extends AbstractDatastream { - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(MultiDatastream.class); - private String name; - private String description; - private String observationType = "http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_ComplexObservation"; - private Map properties; private List multiObservationDataTypes; private List unitOfMeasurements; - private Polygon observedArea; // reference to GeoJSON library - private TimeInterval phenomenonTime; - private TimeInterval resultTime; - private Sensor sensor; private EntitySet observedProperties; - private Thing thing; - private EntitySet observations; - private boolean setName; - private boolean setDescription; - private boolean setObservationType; private boolean setMultiObservationDataTypes; private boolean setUnitOfMeasurements; - private boolean setObservedArea; - private boolean setPhenomenonTime; - private boolean setResultTime; - private boolean setSensor; private boolean setObservedProperties; - private boolean setThing; - private boolean setProperties; public MultiDatastream() { - this.observations = new EntitySetImpl<>(EntityType.Observation); - this.observedProperties = new EntitySetImpl<>(EntityType.ObservedProperty); - this.unitOfMeasurements = new ArrayList<>(); - this.multiObservationDataTypes = new ArrayList<>(); + this(null); } - public MultiDatastream(Id id, - String selfLink, - String navigationLink, - String name, - String description, - Map properties, - List multiObservationDataTypes, - List unitOfMeasurements, - Polygon observedArea, - TimeInterval phenomenonTime, - TimeInterval resultTime, - Sensor sensor, - EntitySet observedProperties, - Thing thing, - EntitySet observations) { - super(id, selfLink, navigationLink); - this.name = name; - this.description = description; - this.multiObservationDataTypes = multiObservationDataTypes; - this.unitOfMeasurements = unitOfMeasurements; - this.observedArea = observedArea; - this.phenomenonTime = phenomenonTime; - this.resultTime = resultTime; - this.sensor = sensor; - this.observedProperties = observedProperties; - this.thing = thing; - this.observations = observations; - if (properties != null && !properties.isEmpty()) { - this.properties = new HashMap<>(properties); - } + public MultiDatastream(Id id) { + super(id); + observedProperties = new EntitySetImpl<>(EntityType.OBSERVEDPROPERTY); + unitOfMeasurements = new ArrayList<>(); + multiObservationDataTypes = new ArrayList<>(); + setObservationTypeIntern(ObservationType.COMPLEX_OBSERVATION.getCode()); } @Override public EntityType getEntityType() { - return EntityType.MultiDatastream; - } - - @Override - public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException { - ResourcePathElement parent = containingSet.getParent(); - if (parent != null && parent instanceof EntityPathElement) { - EntityPathElement parentEntity = (EntityPathElement) parent; - Id parentId = parentEntity.getId(); - if (parentId != null) { - switch (parentEntity.getEntityType()) { - case Sensor: - setSensor(new SensorBuilder().setId(parentId).build()); - LOGGER.debug("Set sensorId to {}.", parentId); - break; - - case Thing: - setThing(new ThingBuilder().setId(parentId).build()); - LOGGER.debug("Set thingId to {}.", parentId); - break; - } - } - } - super.complete(containingSet); + return EntityType.MULTIDATASTREAM; } @Override @@ -148,11 +64,10 @@ public void complete(boolean entityPropertiesOnly) throws IncompleteEntityExcept if (unitOfMeasurements.size() != multiObservationDataTypes.size()) { throw new IllegalStateException("Size of list of unitOfMeasurements (" + unitOfMeasurements.size() + ") is not equal to size of multiObservationDataTypes (" + multiObservationDataTypes.size() + ")."); } - if (!entityPropertiesOnly) { - if (observedProperties.size() != multiObservationDataTypes.size()) { - throw new IllegalStateException("Size of list of observedProperties (" + observedProperties.size() + ") is not equal to size of multiObservationDataTypes (" + multiObservationDataTypes.size() + ")."); - } + if (!entityPropertiesOnly && observedProperties.size() != multiObservationDataTypes.size()) { + throw new IllegalStateException("Size of list of observedProperties (" + observedProperties.size() + ") is not equal to size of multiObservationDataTypes (" + multiObservationDataTypes.size() + ")."); } + String observationType = getObservationType(); if (observationType == null || !observationType.equalsIgnoreCase("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_ComplexObservation")) { throw new IllegalStateException("ObservationType must be http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_ComplexObservation."); } @@ -161,32 +76,8 @@ public void complete(boolean entityPropertiesOnly) throws IncompleteEntityExcept @Override public void setEntityPropertiesSet() { - setDescription = true; - setObservationType = true; + super.setEntityPropertiesSet(); setUnitOfMeasurements = true; - setProperties = true; - } - - public String getName() { - return name; - } - - /** - * @return the description - */ - public String getDescription() { - return description; - } - - /** - * @return the observationType - */ - public String getObservationType() { - return observationType; - } - - public Map getProperties() { - return properties; } public List getMultiObservationDataTypes() { @@ -194,62 +85,40 @@ public List getMultiObservationDataTypes() { } /** - * @return the unitOfMeasurement - */ - public List getUnitOfMeasurements() { - return unitOfMeasurements; - } - - /** - * @return the observedArea - */ - public Polygon getObservedArea() { - return observedArea; - } - - /** - * @return the phenomenonTime - */ - public TimeInterval getPhenomenonTime() { - return phenomenonTime; - } - - /** - * @return the resultTime + * @param observationTypes the observationTypes to set */ - public TimeInterval getResultTime() { - return resultTime; + public void setMultiObservationDataTypes(List observationTypes) { + this.multiObservationDataTypes = observationTypes; + setMultiObservationDataTypes = multiObservationDataTypes != null; } /** - * @return the sensor + * @return the setMultiObservationDataTypes */ - public Sensor getSensor() { - return sensor; + public boolean isSetMultiObservationDataTypes() { + return setMultiObservationDataTypes; } /** - * @param observedArea the observedArea to set + * @return the unitOfMeasurements */ - public void setObservedArea(Polygon observedArea) { - this.observedArea = observedArea; - setObservedArea = true; + public List getUnitOfMeasurements() { + return unitOfMeasurements; } /** - * @param phenomenonTime the phenomenonTime to set + * @param unitsOfMeasurement the unitsOfMeasurement to set */ - public void setPhenomenonTime(TimeInterval phenomenonTime) { - this.phenomenonTime = phenomenonTime; - setPhenomenonTime = true; + public void setUnitOfMeasurements(List unitsOfMeasurement) { + this.unitOfMeasurements = unitsOfMeasurement; + setUnitOfMeasurements = unitOfMeasurements != null; } /** - * @param resultTime the resultTime to set + * @return the setUnitOfMeasurements */ - public void setResultTime(TimeInterval resultTime) { - this.resultTime = resultTime; - setResultTime = true; + public boolean isSetUnitOfMeasurements() { + return setUnitOfMeasurements; } /** @@ -259,90 +128,24 @@ public EntitySet getObservedProperties() { return observedProperties; } - public void setName(String name) { - this.name = name; - setName = true; - } - - /** - * @param description the description to set - */ - public void setDescription(String description) { - this.description = description; - setDescription = true; - } - - /** - * @param observationType the observationType to set - */ - public void setObservationType(String observationType) { - this.observationType = observationType; - setObservationType = true; - } - - /** - * @param observationTypes the observationTypes to set - */ - public void setMultiObservationDataTypes(List observationTypes) { - this.multiObservationDataTypes = observationTypes; - setMultiObservationDataTypes = true; - } - - /** - * @param unitsOfMeasurement the unitsOfMeasurement to set - */ - public void setUnitOfMeasurements(List unitsOfMeasurement) { - this.unitOfMeasurements = unitsOfMeasurement; - setUnitOfMeasurements = true; - } - - /** - * @param sensor the sensor to set - */ - public void setSensor(Sensor sensor) { - this.sensor = sensor; - setSensor = true; - } - /** * @param observedProperties the observedProperty to set */ public void setObservedProperties(EntitySet observedProperties) { this.observedProperties = observedProperties; - setObservedProperties = true; - } - - /** - * @return the thing - */ - public Thing getThing() { - return thing; + setObservedProperties = observedProperties != null; } /** - * @param thing the thing to set + * @return the setObservedProperty */ - public void setThing(Thing thing) { - this.thing = thing; - setThing = true; + public boolean isSetObservedProperties() { + return setObservedProperties; } @Override public int hashCode() { - int hash = 5; - hash = 29 * hash + Objects.hashCode(this.name); - hash = 29 * hash + Objects.hashCode(this.description); - hash = 29 * hash + Objects.hashCode(this.observationType); - hash = 29 * hash + Objects.hashCode(this.multiObservationDataTypes); - hash = 29 * hash + Objects.hashCode(this.unitOfMeasurements); - hash = 29 * hash + Objects.hashCode(this.observedArea); - hash = 29 * hash + Objects.hashCode(this.phenomenonTime); - hash = 29 * hash + Objects.hashCode(this.resultTime); - hash = 29 * hash + Objects.hashCode(this.sensor); - hash = 29 * hash + Objects.hashCode(this.observedProperties); - hash = 29 * hash + Objects.hashCode(this.thing); - hash = 29 * hash + Objects.hashCode(this.properties); - return hash; + return Objects.hash(super.hashCode(), multiObservationDataTypes, unitOfMeasurements, observedProperties); } @Override @@ -357,140 +160,10 @@ public boolean equals(Object obj) { return false; } final MultiDatastream other = (MultiDatastream) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.description, other.description)) { - return false; - } - if (!Objects.equals(this.observationType, other.observationType)) { - return false; - } - if (!Objects.equals(this.observedArea, other.observedArea)) { - return false; - } - if (!Objects.equals(this.phenomenonTime, other.phenomenonTime)) { - return false; - } - if (!Objects.equals(this.resultTime, other.resultTime)) { - return false; - } - if (!Objects.equals(this.sensor, other.sensor)) { - return false; - } - if (!Objects.equals(this.observedProperties, other.observedProperties)) { - return false; - } - if (!Objects.equals(this.thing, other.thing)) { - return false; - } - if (!Objects.equals(this.properties, other.properties)) { - return false; - } - return true; - } - - /** - * @return the observations - */ - public EntitySet getObservations() { - return observations; - } - - /** - * @param observations the observations to set - */ - public void setObservations(EntitySet observations) { - this.observations = observations; - } - - public void setProperties(Map properties) { - if (properties != null && properties.isEmpty()) { - properties = null; - } - this.properties = properties; - setProperties = true; - } - - public boolean isSetName() { - return setName; - } - - /** - * @return the setDescription - */ - public boolean isSetDescription() { - return setDescription; - } - - /** - * @return the setObservationType - */ - public boolean isSetObservationType() { - return setObservationType; - } - - /** - * @return the setMultiObservationDataTypes - */ - public boolean isSetMultiObservationDataTypes() { - return setMultiObservationDataTypes; - } - - /** - * @return the setObservedArea - */ - public boolean isSetObservedArea() { - return setObservedArea; - } - - /** - * @return the setPhenomenonTime - */ - public boolean isSetPhenomenonTime() { - return setPhenomenonTime; - } - - /** - * @return the setResultTime - */ - public boolean isSetResultTime() { - return setResultTime; - } - - /** - * @return the setSensor - */ - public boolean isSetSensor() { - return setSensor; - } - - /** - * @return the setObservedProperty - */ - public boolean isSetObservedProperties() { - return setObservedProperties; - } - - public boolean isSetProperties() { - return setProperties; - } - - /** - * @return the setThing - */ - public boolean isSetThing() { - return setThing; - } - - /** - * @return the setUnitOfMeasurements - */ - public boolean isSetUnitOfMeasurements() { - return setUnitOfMeasurements; + return super.equals(other) + && Objects.equals(multiObservationDataTypes, other.multiObservationDataTypes) + && Objects.equals(observedProperties, other.observedProperties) + && Objects.equals(unitOfMeasurements, other.unitOfMeasurements); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Observation.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Observation.java index 9f2226660..39a1ce6a9 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Observation.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Observation.java @@ -17,20 +17,16 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.builder.DatastreamBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.FeatureOfInterestBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.MultiDatastreamBuilder; import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.slf4j.Logger; @@ -38,7 +34,7 @@ /** * - * @author jab + * @author jab, scf */ public class Observation extends AbstractEntity { @@ -67,61 +63,44 @@ public class Observation extends AbstractEntity { private boolean setFeatureOfInterest; public Observation() { + this(null); } - public Observation(Id id, - String selfLink, - String navigationLink, - TimeValue phenomenonTime, - TimeInstant resultTime, - Object result, - Object resultQuality, - TimeInterval validTime, - Map parameters, - Datastream datastream, - MultiDatastream multiDatastreams, - FeatureOfInterest featureOfInterest) { - super(id, selfLink, navigationLink); - this.phenomenonTime = phenomenonTime; - this.resultTime = resultTime; - this.result = result; - this.resultQuality = resultQuality; - this.validTime = validTime; - if (parameters != null && !parameters.isEmpty()) { - this.parameters = new HashMap<>(parameters); - } - this.datastream = datastream; - this.multiDatastream = multiDatastreams; - this.featureOfInterest = featureOfInterest; + public Observation(Id id) { + super(id); } @Override public EntityType getEntityType() { - return EntityType.Observation; + return EntityType.OBSERVATION; } @Override public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException { ResourcePathElement parent = containingSet.getParent(); - if (parent != null && parent instanceof EntityPathElement) { + if (parent instanceof EntityPathElement) { EntityPathElement parentEntity = (EntityPathElement) parent; Id parentId = parentEntity.getId(); if (parentId != null) { switch (parentEntity.getEntityType()) { - case Datastream: - setDatastream(new DatastreamBuilder().setId(parentId).build()); + case DATASTREAM: + setDatastream(new Datastream(parentId)); LOGGER.debug("Set datastreamId to {}.", parentId); break; - case MultiDatastream: - setMultiDatastream(new MultiDatastreamBuilder().setId(parentId).build()); + case MULTIDATASTREAM: + setMultiDatastream(new MultiDatastream(parentId)); LOGGER.debug("Set multiDatastreamId to {}.", parentId); break; - case FeatureOfInterest: - setFeatureOfInterest(new FeatureOfInterestBuilder().setId(parentId).build()); + case FEATUREOFINTEREST: + setFeatureOfInterest(new FeatureOfInterest(parentId)); LOGGER.debug("Set featureOfInterestId to {}.", parentId); break; + + default: + LOGGER.error("Incorrect 'parent' entity type for {}: {}", getEntityType(), parentEntity.getEntityType()); + break; } } } @@ -157,135 +136,137 @@ public TimeValue getPhenomenonTime() { return phenomenonTime; } - public TimeInstant getResultTime() { - return resultTime; - } - - public Object getResult() { - return result; + public void setPhenomenonTime(TimeValue phenomenonTime) { + this.phenomenonTime = phenomenonTime; + setPhenomenonTime = phenomenonTime != null; } - public Object getResultQuality() { - return resultQuality; + public boolean isSetPhenomenonTime() { + return setPhenomenonTime; } - public TimeInterval getValidTime() { - return validTime; + public TimeInstant getResultTime() { + return resultTime; } - public Map getParameters() { - return parameters; + public void setResultTime(TimeInstant resultTime) { + this.resultTime = resultTime; + setResultTime = resultTime != null; } - public Datastream getDatastream() { - return datastream; + public boolean isSetResultTime() { + return setResultTime; } - public MultiDatastream getMultiDatastream() { - return multiDatastream; + public Object getResult() { + return result; } - public FeatureOfInterest getFeatureOfInterest() { - return featureOfInterest; + public void setResult(Object result) { + this.result = result; + setResult = true; } - public boolean isSetPhenomenonTime() { - return setPhenomenonTime; + public boolean isSetResult() { + return setResult; } - public boolean isSetResultTime() { - return setResultTime; + public Object getResultQuality() { + return resultQuality; } - public boolean isSetResult() { - return setResult; + public void setResultQuality(Object resultQuality) { + this.resultQuality = resultQuality; + setResultQuality = resultQuality != null; } public boolean isSetResultQuality() { return setResultQuality; } - public boolean isSetValidTime() { - return setValidTime; + public TimeInterval getValidTime() { + return validTime; } - public boolean isSetParameters() { - return setParameters; + public void setValidTime(TimeInterval validTime) { + this.validTime = validTime; + setValidTime = validTime != null; } - public boolean isSetDatastream() { - return setDatastream; + public boolean isSetValidTime() { + return setValidTime; } - public boolean isSetMultiDatastream() { - return setMultiDatastream; + public Map getParameters() { + return parameters; } - public boolean isSetFeatureOfInterest() { - return setFeatureOfInterest; + public void setParameters(Map parameters) { + if (parameters != null && parameters.isEmpty()) { + this.parameters = null; + } else { + this.parameters = parameters; + } + setParameters = true; } - public void setPhenomenonTime(TimeValue phenomenonTime) { - this.phenomenonTime = phenomenonTime; - setPhenomenonTime = true; + public boolean isSetParameters() { + return setParameters; } - public void setResultTime(TimeInstant resultTime) { - this.resultTime = resultTime; - setResultTime = true; + public Datastream getDatastream() { + return datastream; } - public void setResult(Object result) { - this.result = result; - setResult = true; + public void setDatastream(Datastream datastream) { + this.datastream = datastream; + setDatastream = datastream != null; } - public void setResultQuality(Object resultQuality) { - this.resultQuality = resultQuality; - setResultQuality = true; + public boolean isSetDatastream() { + return setDatastream; } - public void setValidTime(TimeInterval validTime) { - this.validTime = validTime; - setValidTime = true; + public MultiDatastream getMultiDatastream() { + return multiDatastream; } - public void setParameters(Map parameters) { - if (parameters != null && parameters.isEmpty()) { - parameters = null; - } - this.parameters = parameters; - setParameters = true; + public void setMultiDatastream(MultiDatastream multiDatastream) { + this.multiDatastream = multiDatastream; + setMultiDatastream = multiDatastream != null; } - public void setDatastream(Datastream datastream) { - this.datastream = datastream; - setDatastream = true; + public boolean isSetMultiDatastream() { + return setMultiDatastream; } - public void setMultiDatastream(MultiDatastream multiDatastream) { - this.multiDatastream = multiDatastream; - setMultiDatastream = true; + public FeatureOfInterest getFeatureOfInterest() { + return featureOfInterest; } public void setFeatureOfInterest(FeatureOfInterest featureOfInterest) { this.featureOfInterest = featureOfInterest; - setFeatureOfInterest = true; + setFeatureOfInterest = featureOfInterest != null; + } + + public boolean isSetFeatureOfInterest() { + return setFeatureOfInterest; } @Override public int hashCode() { - int hash = 7; - hash = 59 * hash + Objects.hashCode(this.phenomenonTime); - hash = 59 * hash + Objects.hashCode(this.resultTime); - hash = 59 * hash + Objects.hashCode(this.result); - hash = 59 * hash + Objects.hashCode(this.resultQuality); - hash = 59 * hash + Objects.hashCode(this.validTime); - hash = 59 * hash + Objects.hashCode(this.parameters); - hash = 59 * hash + Objects.hashCode(this.datastream); - hash = 59 * hash + Objects.hashCode(this.multiDatastream); - hash = 59 * hash + Objects.hashCode(this.featureOfInterest); - return hash; + return Objects.hash( + super.hashCode(), + phenomenonTime, + resultTime, + result, + resultQuality, + validTime, + parameters, + datastream, + multiDatastream, + featureOfInterest + ); } @Override @@ -300,37 +281,16 @@ public boolean equals(Object obj) { return false; } final Observation other = (Observation) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.phenomenonTime, other.phenomenonTime)) { - return false; - } - if (!Objects.equals(this.resultTime, other.resultTime)) { - return false; - } - if (!Objects.equals(this.result, other.result)) { - return false; - } - if (!Objects.equals(this.resultQuality, other.resultQuality)) { - return false; - } - if (!Objects.equals(this.validTime, other.validTime)) { - return false; - } - if (!Objects.equals(this.parameters, other.parameters)) { - return false; - } - if (!Objects.equals(this.datastream, other.datastream)) { - return false; - } - if (!Objects.equals(this.multiDatastream, other.multiDatastream)) { - return false; - } - if (!Objects.equals(this.featureOfInterest, other.featureOfInterest)) { - return false; - } - return true; + return super.equals(other) + && Objects.equals(phenomenonTime, other.phenomenonTime) + && Objects.equals(resultTime, other.resultTime) + && Objects.equals(result, other.result) + && Objects.equals(resultQuality, other.resultQuality) + && Objects.equals(validTime, other.validTime) + && Objects.equals(parameters, other.parameters) + && Objects.equals(datastream, other.datastream) + && Objects.equals(multiDatastream, other.multiDatastream) + && Objects.equals(featureOfInterest, other.featureOfInterest); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ObservedProperty.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ObservedProperty.java index d86d1ab8b..3bfb87ab0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ObservedProperty.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ObservedProperty.java @@ -17,189 +17,71 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.NamedDsHoldingEntity; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; /** * - * @author jab + * @author jab, scf */ -public class ObservedProperty extends AbstractEntity { +public class ObservedProperty extends NamedDsHoldingEntity { - private String name; private String definition; - private String description; - private Map properties; - private EntitySet datastreams; - private EntitySet multiDatastreams; - private boolean setName; private boolean setDefinition; - private boolean setDescription; - private boolean setProperties; public ObservedProperty() { - this.datastreams = new EntitySetImpl<>(EntityType.Datastream); - this.multiDatastreams = new EntitySetImpl<>(EntityType.MultiDatastream); } - public ObservedProperty( - Id id, - String selfLink, - String navigationLink, - String name, - String definition, - String description, - Map properties, - EntitySet datastreams, - EntitySet multiDatastreams) { - super(id, selfLink, navigationLink); - this.name = name; - this.definition = definition; - this.description = description; - this.datastreams = datastreams; - this.multiDatastreams = multiDatastreams; - if (properties != null && !properties.isEmpty()) { - this.properties = new HashMap<>(properties); - } - } - - @Override - public int hashCode() { - int hash = 7; - hash = 29 * hash + Objects.hashCode(this.name); - hash = 29 * hash + Objects.hashCode(this.definition); - hash = 29 * hash + Objects.hashCode(this.description); - hash = 29 * hash + Objects.hashCode(this.datastreams); - hash = 29 * hash + Objects.hashCode(this.multiDatastreams); - hash = 29 * hash + Objects.hashCode(this.properties); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ObservedProperty other = (ObservedProperty) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.definition, other.definition)) { - return false; - } - if (!Objects.equals(this.description, other.description)) { - return false; - } - if (!Objects.equals(this.datastreams, other.datastreams)) { - return false; - } - if (!Objects.equals(this.multiDatastreams, other.multiDatastreams)) { - return false; - } - if (!Objects.equals(this.properties, other.properties)) { - return false; - } - return true; + public ObservedProperty(Id id) { + super(id); } @Override public EntityType getEntityType() { - return EntityType.ObservedProperty; + return EntityType.OBSERVEDPROPERTY; } @Override public void setEntityPropertiesSet() { - setName = true; + super.setEntityPropertiesSet(); setDefinition = true; - setDescription = true; - setProperties = true; - } - - public String getName() { - return name; } public String getDefinition() { return definition; } - public String getDescription() { - return description; - } - - public Map getProperties() { - return properties; - } - - public EntitySet getDatastreams() { - return datastreams; - } - - public EntitySet getMultiDatastreams() { - return multiDatastreams; - } - - public boolean isSetName() { - return setName; + public void setDefinition(String definition) { + this.definition = definition; + setDefinition = definition != null; } public boolean isSetDefinition() { return setDefinition; } - public boolean isSetDescription() { - return setDescription; - } - - public boolean isSetProperties() { - return setProperties; - } - - public void setName(String name) { - this.name = name; - setName = true; - } - - public void setDefinition(String definition) { - this.definition = definition; - setDefinition = true; - } - - public void setDescription(String description) { - this.description = description; - setDescription = true; + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), definition); } - public void setProperties(Map properties) { - if (properties != null && properties.isEmpty()) { - properties = null; + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; } - this.properties = properties; - setProperties = true; - } - - public void setDatastreams(EntitySet datastreams) { - this.datastreams = datastreams; - } - - public void setMultiDatastreams(EntitySet multiDatastreams) { - this.multiDatastreams = multiDatastreams; + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ObservedProperty other = (ObservedProperty) obj; + return super.equals(other) + && Objects.equals(definition, other.definition); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Sensor.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Sensor.java index f3d693f12..ec61dbb4b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Sensor.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Sensor.java @@ -17,210 +17,88 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.NamedDsHoldingEntity; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; /** * - * @author jab + * @author jab, scf */ -public class Sensor extends AbstractEntity { +public class Sensor extends NamedDsHoldingEntity { - private String name; - private String description; private String encodingType; private Object metadata; - private Map properties; - private EntitySet datastreams; - private EntitySet multiDatastreams; - private boolean setName; - private boolean setDescription; private boolean setEncodingType; private boolean setMetadata; - private boolean setProperties; public Sensor() { - this.datastreams = new EntitySetImpl<>(EntityType.Datastream); - this.multiDatastreams = new EntitySetImpl<>(EntityType.MultiDatastream); } - public Sensor( - Id id, - String selfLink, - String navigationLink, - String name, - String description, - String encodingType, - Object metadata, - Map properties, - EntitySet datastreams, - EntitySet multiDatastreams) { - super(id, selfLink, navigationLink); - this.name = name; - this.description = description; - this.encodingType = encodingType; - this.metadata = metadata; - this.datastreams = datastreams; - this.multiDatastreams = multiDatastreams; - if (properties != null && !properties.isEmpty()) { - this.properties = new HashMap<>(properties); - } - } - - @Override - public int hashCode() { - int hash = 5; - hash = 97 * hash + Objects.hashCode(this.name); - hash = 97 * hash + Objects.hashCode(this.description); - hash = 97 * hash + Objects.hashCode(this.encodingType); - hash = 97 * hash + Objects.hashCode(this.metadata); - hash = 97 * hash + Objects.hashCode(this.datastreams); - hash = 97 * hash + Objects.hashCode(this.multiDatastreams); - hash = 97 * hash + Objects.hashCode(this.properties); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Sensor other = (Sensor) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.description, other.description)) { - return false; - } - if (!Objects.equals(this.encodingType, other.encodingType)) { - return false; - } - if (!Objects.equals(this.metadata, other.metadata)) { - return false; - } - if (!Objects.equals(this.datastreams, other.datastreams)) { - return false; - } - if (!Objects.equals(this.multiDatastreams, other.multiDatastreams)) { - return false; - } - if (!Objects.equals(this.properties, other.properties)) { - return false; - } - return true; + public Sensor(Id id) { + super(id); } @Override public EntityType getEntityType() { - return EntityType.Sensor; + return EntityType.SENSOR; } @Override public void setEntityPropertiesSet() { - setDescription = true; + super.setEntityPropertiesSet(); setEncodingType = true; setMetadata = true; - setProperties = true; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; } public String getEncodingType() { return encodingType; } - public Object getMetadata() { - return metadata; - } - - public Map getProperties() { - return properties; - } - - public EntitySet getDatastreams() { - return datastreams; - } - - public EntitySet getMultiDatastreams() { - return multiDatastreams; - } - - public boolean isSetName() { - return setName; - } - - public boolean isSetDescription() { - return setDescription; + public void setEncodingType(String encodingType) { + this.encodingType = encodingType; + setEncodingType = encodingType != null; } public boolean isSetEncodingType() { return setEncodingType; } - public boolean isSetMetadata() { - return setMetadata; - } - - public boolean isSetProperties() { - return setProperties; - } - - public void setName(String name) { - this.name = name; - setName = true; - } - - public void setDescription(String description) { - this.description = description; - setDescription = true; - } - - public void setEncodingType(String encodingType) { - this.encodingType = encodingType; - setEncodingType = true; + public Object getMetadata() { + return metadata; } public void setMetadata(Object metadata) { this.metadata = metadata; - setMetadata = true; + setMetadata = metadata != null; } - public void setProperties(Map properties) { - if (properties != null && properties.isEmpty()) { - properties = null; - } - this.properties = properties; - setProperties = true; + public boolean isSetMetadata() { + return setMetadata; } - public void setDatastreams(EntitySet datastreams) { - this.datastreams = datastreams; + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), encodingType, metadata); } - public void setMultiDatastreams(EntitySet multiDatastreams) { - this.multiDatastreams = multiDatastreams; + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Sensor other = (Sensor) obj; + return super.equals(other) + && Objects.equals(encodingType, other.encodingType) + && Objects.equals(metadata, other.metadata); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Thing.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Thing.java index 68a618e9c..08f368cc1 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Thing.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/Thing.java @@ -17,159 +17,56 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.model.core.AbstractEntity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.NamedDsHoldingEntity; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; /** * - * @author jab + * @author jab, scf */ -public class Thing extends AbstractEntity { +public class Thing extends NamedDsHoldingEntity { - private String name; - private String description; - private Map properties; private EntitySet locations; // 0..* - private EntitySet historicalLocations; // 0..* - private EntitySet datastreams; // 0..* - private EntitySet multiDatastreams; // 0..* - - private boolean setName; - private boolean setDescription; - private boolean setProperties; public Thing() { - this.locations = new EntitySetImpl<>(EntityType.Location); - this.historicalLocations = new EntitySetImpl<>(EntityType.HistoricalLocation); - this.datastreams = new EntitySetImpl<>(EntityType.Datastream); - this.multiDatastreams = new EntitySetImpl<>(EntityType.MultiDatastream); + this(null); } - public Thing(Id id, - String selfLink, - String navigationLink, - String name, - String description, - Map properties, - EntitySet locations, - EntitySet historicalLocations, - EntitySet datastreams, - EntitySet multiDatastreams) { - super(id, selfLink, navigationLink); - this.name = name; - this.description = description; - if (properties != null && !properties.isEmpty()) { - this.properties = new HashMap<>(properties); - } - this.locations = locations; - this.historicalLocations = historicalLocations; - this.datastreams = datastreams; - this.multiDatastreams = multiDatastreams; + public Thing(Id id) { + super(id); + this.locations = new EntitySetImpl<>(EntityType.LOCATION); + this.historicalLocations = new EntitySetImpl<>(EntityType.HISTORICALLOCATION); } @Override public EntityType getEntityType() { - return EntityType.Thing; - } - - @Override - public void setEntityPropertiesSet() { - setDescription = true; - setProperties = true; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Map getProperties() { - return properties; + return EntityType.THING; } public EntitySet getLocations() { return locations; } - public EntitySet getHistoricalLocations() { - return historicalLocations; - } - - public EntitySet getDatastreams() { - return datastreams; - } - - public EntitySet getMultiDatastreams() { - return multiDatastreams; - } - - public boolean isSetName() { - return setName; - } - - public boolean isSetDescription() { - return setDescription; - } - - public boolean isSetProperties() { - return setProperties; - } - - public void setName(String name) { - this.name = name; - setName = true; - } - - public void setDescription(String description) { - this.description = description; - setDescription = true; - } - - public void setProperties(Map properties) { - if (properties != null && properties.isEmpty()) { - properties = null; - } - this.properties = properties; - setProperties = true; - } - public void setLocations(EntitySet locations) { this.locations = locations; } - public void setHistoricalLocations(EntitySet historicalLocations) { - this.historicalLocations = historicalLocations; - } - - public void setDatastreams(EntitySet datastreams) { - this.datastreams = datastreams; + public EntitySet getHistoricalLocations() { + return historicalLocations; } - public void setMultiDatastreams(EntitySet multiDatastreams) { - this.multiDatastreams = multiDatastreams; + public void setHistoricalLocations(EntitySet historicalLocations) { + this.historicalLocations = historicalLocations; } @Override public int hashCode() { - int hash = 7; - hash = 71 * hash + Objects.hashCode(this.name); - hash = 71 * hash + Objects.hashCode(this.description); - hash = 71 * hash + Objects.hashCode(this.properties); - hash = 71 * hash + Objects.hashCode(this.locations); - hash = 71 * hash + Objects.hashCode(this.historicalLocations); - hash = 71 * hash + Objects.hashCode(this.datastreams); - hash = 71 * hash + Objects.hashCode(this.multiDatastreams); - return hash; + return Objects.hash(super.hashCode(), locations, historicalLocations); } @Override @@ -184,30 +81,8 @@ public boolean equals(Object obj) { return false; } final Thing other = (Thing) obj; - if (!super.equals(other)) { - return false; - } - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.description, other.description)) { - return false; - } - if (!Objects.equals(this.properties, other.properties)) { - return false; - } - if (!Objects.equals(this.locations, other.locations)) { - return false; - } - if (!Objects.equals(this.historicalLocations, other.historicalLocations)) { - return false; - } - if (!Objects.equals(this.datastreams, other.datastreams)) { - return false; - } - if (!Objects.equals(this.multiDatastreams, other.multiDatastreams)) { - return false; - } - return true; + return super.equals(other) + && Objects.equals(locations, other.locations) + && Objects.equals(historicalLocations, other.historicalLocations); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/AbstractEntityBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/AbstractEntityBuilder.java index fee6cfecc..8a8896158 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/AbstractEntityBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/AbstractEntityBuilder.java @@ -17,7 +17,7 @@ */ package de.fraunhofer.iosb.ilt.sta.model.builder; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; /** * Abstract base class for implementing an EntityBuilder diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/DatastreamBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/DatastreamBuilder.java index 63f3edd7b..63038fae7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/DatastreamBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/DatastreamBuilder.java @@ -53,12 +53,11 @@ public class DatastreamBuilder extends AbstractEntityBuilder(); - observations = new EntitySetImpl<>(EntityType.Observation); + observations = new EntitySetImpl<>(EntityType.OBSERVATION); } public DatastreamBuilder setObservations(EntitySet observations) { this.observations = observations; - this.unitOfMeasurement = new UnitOfMeasurement(); return this; } @@ -134,22 +133,21 @@ protected DatastreamBuilder getThis() { @Override public Datastream build() { - Datastream ds = new Datastream( - id, - selfLink, - navigationLink, - name, - description, - observationType, - properties, - unitOfMeasurement, - observedArea, - phenomenonTime, - resultTime, - sensor, - observedProperty, - thing, - observations); + Datastream ds = new Datastream(id); + ds.setSelfLink(selfLink); + ds.setNavigationLink(navigationLink); + ds.setName(name); + ds.setDescription(description); + ds.setObservationType(observationType); + ds.setProperties(properties); + ds.setUnitOfMeasurement(unitOfMeasurement); + ds.setObservedArea(observedArea); + ds.setPhenomenonTime(phenomenonTime); + ds.setResultTime(resultTime); + ds.setSensor(sensor); + ds.setObservedProperty(observedProperty); + ds.setThing(thing); + ds.setObservations(observations); ds.setExportObject(isExportObject()); return ds; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/FeatureOfInterestBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/FeatureOfInterestBuilder.java index dbac81111..9fc8d0297 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/FeatureOfInterestBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/FeatureOfInterestBuilder.java @@ -41,7 +41,7 @@ public class FeatureOfInterestBuilder extends AbstractEntityBuilder(); - observations = new EntitySetImpl<>(EntityType.Observation); + observations = new EntitySetImpl<>(EntityType.OBSERVATION); } public FeatureOfInterestBuilder setName(String name) { @@ -91,16 +91,15 @@ protected FeatureOfInterestBuilder getThis() { @Override public FeatureOfInterest build() { - FeatureOfInterest foi = new FeatureOfInterest( - id, - selfLink, - navigationLink, - name, - description, - encodingType, - feature, - properties, - observations); + FeatureOfInterest foi = new FeatureOfInterest(id); + foi.setSelfLink(selfLink); + foi.setNavigationLink(navigationLink); + foi.setName(name); + foi.setDescription(description); + foi.setEncodingType(encodingType); + foi.setFeature(feature); + foi.setProperties(properties); + foi.setObservations(observations); foi.setExportObject(isExportObject()); return foi; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/HistoricalLocationBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/HistoricalLocationBuilder.java index 31f203ea3..8a1ec2260 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/HistoricalLocationBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/HistoricalLocationBuilder.java @@ -37,7 +37,7 @@ public class HistoricalLocationBuilder extends AbstractEntityBuilder locations; public HistoricalLocationBuilder() { - locations = new EntitySetImpl<>(EntityType.Location); + locations = new EntitySetImpl<>(EntityType.LOCATION); } public HistoricalLocationBuilder setTime(TimeInstant time) { @@ -67,7 +67,12 @@ protected HistoricalLocationBuilder getThis() { @Override public HistoricalLocation build() { - HistoricalLocation hl = new HistoricalLocation(id, selfLink, navigationLink, time, thing, locations); + HistoricalLocation hl = new HistoricalLocation(id); + hl.setSelfLink(selfLink); + hl.setNavigationLink(navigationLink); + hl.setTime(time); + hl.setThing(thing); + hl.setLocations(locations); hl.setExportObject(isExportObject()); return hl; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/LocationBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/LocationBuilder.java index 47833eb27..6c607f6e8 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/LocationBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/LocationBuilder.java @@ -43,8 +43,8 @@ public class LocationBuilder extends AbstractEntityBuilder(); - things = new EntitySetImpl<>(EntityType.Thing); - historicalLocations = new EntitySetImpl<>(EntityType.HistoricalLocation); + things = new EntitySetImpl<>(EntityType.THING); + historicalLocations = new EntitySetImpl<>(EntityType.HISTORICALLOCATION); } public LocationBuilder setName(String name) { @@ -104,17 +104,16 @@ protected LocationBuilder getThis() { @Override public Location build() { - Location l = new Location( - id, - selfLink, - navigationLink, - name, - description, - encodingType, - location, - properties, - historicalLocations, - things); + Location l = new Location(id); + l.setSelfLink(selfLink); + l.setNavigationLink(navigationLink); + l.setName(name); + l.setDescription(description); + l.setEncodingType(encodingType); + l.setLocation(location); + l.setProperties(properties); + l.setHistoricalLocations(historicalLocations); + l.setThings(things); l.setExportObject(isExportObject()); return l; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/MultiDatastreamBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/MultiDatastreamBuilder.java index cc5637811..ba5c4dfcb 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/MultiDatastreamBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/MultiDatastreamBuilder.java @@ -42,6 +42,7 @@ public class MultiDatastreamBuilder extends AbstractEntityBuilder multiObservationDataTypes; private List unitOfMeasurements; private Polygon observedArea; @@ -55,8 +56,8 @@ public class MultiDatastreamBuilder extends AbstractEntityBuilder(); - observations = new EntitySetImpl<>(EntityType.Observation); - observedProperties = new EntitySetImpl<>(EntityType.ObservedProperty); + observations = new EntitySetImpl<>(EntityType.OBSERVATION); + observedProperties = new EntitySetImpl<>(EntityType.OBSERVEDPROPERTY); unitOfMeasurements = new ArrayList<>(); multiObservationDataTypes = new ArrayList<>(); } @@ -91,6 +92,10 @@ public MultiDatastreamBuilder setDescription(String description) { return this; } + public void setObservationType(String observationType) { + this.observationType = observationType; + } + public MultiDatastreamBuilder setMultiObservationDataTypes(List multiObservationDataTypes) { this.multiObservationDataTypes = multiObservationDataTypes; return this; @@ -153,22 +158,24 @@ protected MultiDatastreamBuilder getThis() { @Override public MultiDatastream build() { - MultiDatastream mds = new MultiDatastream( - id, - selfLink, - navigationLink, - name, - description, - properties, - multiObservationDataTypes, - unitOfMeasurements, - observedArea, - phenomenonTime, - resultTime, - sensor, - observedProperties, - thing, - observations); + MultiDatastream mds = new MultiDatastream(id); + mds.setSelfLink(selfLink); + mds.setNavigationLink(navigationLink); + mds.setName(name); + mds.setDescription(description); + mds.setProperties(properties); + if (observationType != null) { + mds.setObservationType(observationType); + } + mds.setMultiObservationDataTypes(multiObservationDataTypes); + mds.setUnitOfMeasurements(unitOfMeasurements); + mds.setObservedArea(observedArea); + mds.setPhenomenonTime(phenomenonTime); + mds.setResultTime(resultTime); + mds.setSensor(sensor); + mds.setObservedProperties(observedProperties); + mds.setThing(thing); + mds.setObservations(observations); mds.setExportObject(isExportObject()); return mds; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservationBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservationBuilder.java index 5c9ef00f5..6030f23dd 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservationBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservationBuilder.java @@ -105,19 +105,18 @@ protected ObservationBuilder getThis() { @Override public Observation build() { - Observation o = new Observation( - id, - selfLink, - navigationLink, - phenomenonTime, - resultTime, - result, - resultQuality, - validTime, - parameters, - datastream, - multiDatastream, - featureOfInterest); + Observation o = new Observation(id); + o.setSelfLink(selfLink); + o.setNavigationLink(navigationLink); + o.setPhenomenonTime(phenomenonTime); + o.setResultTime(resultTime); + o.setResult(result); + o.setResultQuality(resultQuality); + o.setValidTime(validTime); + o.setParameters(parameters); + o.setDatastream(datastream); + o.setMultiDatastream(multiDatastream); + o.setFeatureOfInterest(featureOfInterest); o.setExportObject(isExportObject()); return o; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservedPropertyBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservedPropertyBuilder.java index 2a77c3195..e16261d75 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservedPropertyBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ObservedPropertyBuilder.java @@ -42,8 +42,8 @@ public class ObservedPropertyBuilder extends AbstractEntityBuilder(); - datastreams = new EntitySetImpl<>(EntityType.Datastream); - multiDatastreams = new EntitySetImpl<>(EntityType.MultiDatastream); + datastreams = new EntitySetImpl<>(EntityType.DATASTREAM); + multiDatastreams = new EntitySetImpl<>(EntityType.MULTIDATASTREAM); } public ObservedPropertyBuilder setName(String name) { @@ -102,16 +102,15 @@ public ObservedPropertyBuilder addMultiDatastream(MultiDatastream multiDatastrea @Override public ObservedProperty build() { - ObservedProperty op = new ObservedProperty( - id, - selfLink, - navigationLink, - name, - definition, - description, - properties, - datastreams, - multiDatastreams); + ObservedProperty op = new ObservedProperty(id); + op.setSelfLink(selfLink); + op.setNavigationLink(navigationLink); + op.setName(name); + op.setDefinition(definition); + op.setDescription(description); + op.setProperties(properties); + op.setDatastreams(datastreams); + op.setMultiDatastreams(multiDatastreams); op.setExportObject(isExportObject()); return op; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/SensorBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/SensorBuilder.java index f0414cf8f..9ebb6423b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/SensorBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/SensorBuilder.java @@ -43,8 +43,8 @@ public class SensorBuilder extends AbstractEntityBuilder public SensorBuilder() { properties = new HashMap<>(); - datastreams = new EntitySetImpl<>(EntityType.Datastream); - multiDatastreams = new EntitySetImpl<>(EntityType.MultiDatastream); + datastreams = new EntitySetImpl<>(EntityType.DATASTREAM); + multiDatastreams = new EntitySetImpl<>(EntityType.MULTIDATASTREAM); } public SensorBuilder setName(String name) { @@ -104,17 +104,16 @@ protected SensorBuilder getThis() { @Override public Sensor build() { - Sensor sensor = new Sensor( - id, - selfLink, - navigationLink, - name, - description, - encodingType, - metadata, - properties, - datastreams, - multiDatastreams); + Sensor sensor = new Sensor(id); + sensor.setSelfLink(selfLink); + sensor.setNavigationLink(navigationLink); + sensor.setName(name); + sensor.setDescription(description); + sensor.setEncodingType(encodingType); + sensor.setMetadata(metadata); + sensor.setProperties(properties); + sensor.setDatastreams(datastreams); + sensor.setMultiDatastreams(multiDatastreams); sensor.setExportObject(isExportObject()); return sensor; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ThingBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ThingBuilder.java index f032233e4..cd6393bdc 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ThingBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/ThingBuilder.java @@ -45,10 +45,10 @@ public class ThingBuilder extends AbstractEntityBuilder { public ThingBuilder() { properties = new HashMap<>(); - locations = new EntitySetImpl<>(EntityType.Location); - historicalLocations = new EntitySetImpl<>(EntityType.HistoricalLocation); - datastreams = new EntitySetImpl<>(EntityType.Datastream); - multiDatastreams = new EntitySetImpl<>(EntityType.MultiDatastream); + locations = new EntitySetImpl<>(EntityType.LOCATION); + historicalLocations = new EntitySetImpl<>(EntityType.HISTORICALLOCATION); + datastreams = new EntitySetImpl<>(EntityType.DATASTREAM); + multiDatastreams = new EntitySetImpl<>(EntityType.MULTIDATASTREAM); } public ThingBuilder setName(String name) { @@ -118,17 +118,16 @@ protected ThingBuilder getThis() { @Override public Thing build() { - Thing thing = new Thing( - id, - selfLink, - navigationLink, - name, - description, - properties, - locations, - historicalLocations, - datastreams, - multiDatastreams); + Thing thing = new Thing(id); + thing.setSelfLink(selfLink); + thing.setNavigationLink(navigationLink); + thing.setName(name); + thing.setDescription(description); + thing.setProperties(properties); + thing.setLocations(locations); + thing.setHistoricalLocations(historicalLocations); + thing.setDatastreams(datastreams); + thing.setMultiDatastreams(multiDatastreams); thing.setExportObject(isExportObject()); return thing; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/UnitOfMeasurementBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/UnitOfMeasurementBuilder.java index 87144a929..75dc23b06 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/UnitOfMeasurementBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/builder/UnitOfMeasurementBuilder.java @@ -30,9 +30,6 @@ public class UnitOfMeasurementBuilder { private String symbol; private String definition; - public UnitOfMeasurementBuilder() { - } - public UnitOfMeasurementBuilder setName(String name) { this.name = name; return this; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/AbstractEntity.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/AbstractEntity.java index 859bfceeb..a27d82585 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/AbstractEntity.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/AbstractEntity.java @@ -19,14 +19,15 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.lang3.reflect.MethodUtils; import org.slf4j.LoggerFactory; /** @@ -41,42 +42,62 @@ public abstract class AbstractEntity implements Entity { */ private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AbstractEntity.class); - public AbstractEntity() { - } - - public AbstractEntity(Id id, String selfLink, String navigationLink) { - this.id = id; - this.selfLink = selfLink; - this.navigationLink = navigationLink; - } @JsonProperty("@iot.id") - protected Id id; + private Id id; @JsonProperty("@iot.selfLink") - protected String selfLink; + private String selfLink; @JsonIgnore - protected String navigationLink; + private String navigationLink; @JsonIgnore private boolean exportObject = true; + @JsonIgnore + private Set selectedProperties; + + /** + * Flag indicating the Id was set by the user. + */ + @JsonIgnore + private boolean setId; + /** + * Flag indicating the selfLink was set by the user. + */ + @JsonIgnore + private boolean setSelfLink; + + public AbstractEntity(Id id) { + setId(id); + } + @Override public Id getId() { return id; } + /** + * @param id the id to set + */ @Override - public String getSelfLink() { - return selfLink; + public final void setId(Id id) { + this.id = id; + setId = true; } /** - * @param id the id to set + * Flag indicating the Id was set by the user. + * + * @return Flag indicating the Id was set by the user. */ + public boolean isSetId() { + return setId; + } + @Override - public void setId(Id id) { - this.id = id; + public String getSelfLink() { + return selfLink; } /** @@ -85,39 +106,16 @@ public void setId(Id id) { @Override public void setSelfLink(String selfLink) { this.selfLink = selfLink; + setSelfLink = true; } - @Override - public int hashCode() { - int hash = 5; - hash = 89 * hash + Objects.hashCode(this.id); - hash = 89 * hash + Objects.hashCode(this.selfLink); - hash = 89 * hash + Objects.hashCode(this.navigationLink); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AbstractEntity other = (AbstractEntity) obj; - if (!Objects.equals(this.id, other.id)) { - return false; - } - if (!Objects.equals(this.selfLink, other.selfLink)) { - return false; - } - if (!Objects.equals(this.navigationLink, other.navigationLink)) { - return false; - } - return true; + /** + * Flag indicating the selfLink was set by the user. + * + * @return Flag indicating the selfLink was set by the user. + */ + public boolean isSetSelfLink() { + return setSelfLink; } /** @@ -136,14 +134,33 @@ public void setNavigationLink(String navigationLink) { this.navigationLink = navigationLink; } + @Override + public Set getSelectedPropertyNames() { + return selectedProperties; + } + + @Override + public void setSelectedPropertyNames(Set selectedProperties) { + this.selectedProperties = selectedProperties; + } + + @Override + public void setSelectedProperties(Set selectedProperties) { + setSelectedPropertyNames( + selectedProperties + .stream() + .map(Property::getJsonName) + .collect(Collectors.toSet()) + ); + } + @Override public Object getProperty(Property property) { String methodName = property.getGetterName(); try { - Method getMethod = this.getClass().getMethod(methodName, null); - return getMethod.invoke(this, null); + return MethodUtils.invokeExactMethod(this, methodName); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - LOGGER.error("Failed to find or execute method " + methodName, ex); + LOGGER.error("Failed to find or execute getter method " + methodName, ex); return null; } } @@ -152,21 +169,33 @@ public Object getProperty(Property property) { public void setProperty(Property property, Object value) { String methodName = property.getSetterName(); try { - for (Method m : this.getClass().getMethods()) { - if (m.getParameterCount() == 1 && methodName.equals(m.getName())) { - try { - m.invoke(this, value); - return; - } catch (SecurityException | IllegalAccessException | InvocationTargetException | IllegalArgumentException e) { - LOGGER.trace("Wrong setter method."); - } - } - } - } catch (SecurityException ex) { + MethodUtils.invokeMethod(this, methodName, value); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + LOGGER.error("Failed to find or execute setter method " + methodName, ex); + } + } + + @Override + public void unsetProperty(Property property) { + String methodName = property.getSetterName(); + try { + MethodUtils.invokeMethod(this, methodName, (Object) null); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { LOGGER.error("Failed to find or execute method " + methodName, ex); } } + @Override + public boolean isSetProperty(Property property) { + String isSetMethodName = property.getIsSetName(); + try { + return (boolean) MethodUtils.invokeMethod(this, isSetMethodName); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + LOGGER.error("Failed to find or execute 'isSet' method " + isSetMethodName, ex); + } + return false; + } + @Override public boolean isExportObject() { return exportObject; @@ -177,23 +206,6 @@ public void setExportObject(boolean exportObject) { this.exportObject = exportObject; } - @Override - public void unsetProperty(Property property) { - String methodName = property.getSetterName(); - try { - Method[] methods = this.getClass().getMethods(); - for (Method method : methods) { - if (method.getName().equalsIgnoreCase(methodName) && method.getParameterCount() == 1) { - method.invoke(this, new Object[]{null}); - return; - } - } - LOGGER.error("Failed to find method {}.", methodName); - } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - LOGGER.error("Failed to find or execute method " + methodName, ex); - } - } - @Override public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException { EntityType type = containingSet.getEntityType(); @@ -202,4 +214,27 @@ public void complete(EntitySetPathElement containingSet) throws IncompleteEntity } complete(); } + + @Override + public int hashCode() { + return Objects.hash(id, selfLink, navigationLink); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AbstractEntity other = (AbstractEntity) obj; + return Objects.equals(this.id, other.id) + && Objects.equals(this.selfLink, other.selfLink) + && Objects.equals(this.navigationLink, other.navigationLink); + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/Entity.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/Entity.java index fcb376859..5e8822054 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/Entity.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/Entity.java @@ -18,7 +18,6 @@ package de.fraunhofer.iosb.ilt.sta.model.core; import com.fasterxml.jackson.annotation.JsonIgnore; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; @@ -26,6 +25,7 @@ import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import java.util.Set; /** * Interface defining basic methods of an Entity. @@ -48,6 +48,38 @@ public interface Entity extends NavigableElement { @JsonIgnore public EntityType getEntityType(); + /** + * Get the list of names of properties that should be serialised. + * + * @return The list of property names that should be serialised when + * converting this Entity to JSON. + */ + public Set getSelectedPropertyNames(); + + /** + * Set the names of the properties that should be serialised. + * + * @param selectedProperties the names of the properties that should be + * serialised. + */ + public void setSelectedPropertyNames(Set selectedProperties); + + /** + * Set the properties that should be serialised. + * + * @param selectedProperties the properties that should be serialised. + */ + public void setSelectedProperties(Set selectedProperties); + + /** + * Returns true if the property is explicitly set to a value, even if this + * value is null. + * + * @param property the property to check. + * @return true if the property is explicitly set. + */ + public boolean isSetProperty(Property property); + public Object getProperty(Property property); public void setProperty(Property property, Object value); @@ -70,7 +102,7 @@ public interface Entity extends NavigableElement { * @throws IllegalStateException If the containing set is not of the type * that can contain this entity. */ - public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException, IllegalStateException; + public void complete(EntitySetPathElement containingSet) throws IncompleteEntityException; /** * Checks if all required properties are non-null. @@ -80,7 +112,7 @@ public interface Entity extends NavigableElement { * @throws IllegalStateException If any of the required properties are * incorrect (i.e. Observation with both a Datastream and a MultiDatastream. */ - public default void complete() throws IncompleteEntityException, IllegalStateException { + public default void complete() throws IncompleteEntityException { complete(false); } @@ -94,17 +126,14 @@ public default void complete() throws IncompleteEntityException, IllegalStateExc * @throws IllegalStateException If any of the required properties are * incorrect (i.e. Observation with both a Datastream and a MultiDatastream. */ - public default void complete(boolean entityPropertiesOnly) throws IncompleteEntityException, IllegalStateException { + public default void complete(boolean entityPropertiesOnly) throws IncompleteEntityException { EntityType type = getEntityType(); for (Property property : type.getPropertySet()) { if (entityPropertiesOnly && !(property instanceof EntityProperty)) { continue; } - if (type.isRequired(property)) { - Object value = getProperty(property); - if (value == null) { - throw new IncompleteEntityException("Missing required property '" + property + "'"); - } + if (type.isRequired(property) && !isSetProperty(property)) { + throw new IncompleteEntityException("Missing required property '" + property.getJsonName() + "'"); } } } @@ -120,8 +149,7 @@ public default ResourcePath getPath() { epe.setEntityType(type); epe.setId(getId()); ResourcePath resourcePath = new ResourcePath(); - resourcePath.getPathElements().add(epe); - resourcePath.setMainElement(epe); + resourcePath.addPathElement(epe, true, false); return resourcePath; } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/EntitySetImpl.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/EntitySetImpl.java index ce0d7fffa..8d4dca9b7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/EntitySetImpl.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/EntitySetImpl.java @@ -149,10 +149,7 @@ public void clear() { @Override public int hashCode() { - int hash = 7; - hash = 79 * hash + Objects.hashCode(this.data); - hash = 79 * hash + Objects.hashCode(this.navigationLink); - return hash; + return Objects.hash(data, navigationLink); } @Override @@ -170,10 +167,7 @@ public boolean equals(Object obj) { if (!Objects.equals(this.navigationLink, other.navigationLink)) { return false; } - if (!Objects.equals(this.data, other.data)) { - return false; - } - return true; + return Objects.equals(this.data, other.data); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/Id.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/Id.java similarity index 92% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/Id.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/Id.java index b36d07b12..ca5724943 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/Id.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/Id.java @@ -15,11 +15,10 @@ * 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.sta.model.id; +package de.fraunhofer.iosb.ilt.sta.model.core; import com.fasterxml.jackson.annotation.JsonValue; import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; -//import de.fraunhofer.iosb.ilt.sta.dao.BasicPersistenceType; /** * @@ -42,4 +41,5 @@ public interface Id { public Object asBasicPersistenceType(); public void fromBasicPersitenceType(Object data); + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/LongId.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/IdLong.java similarity index 75% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/LongId.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/IdLong.java index 37d1dc852..bfcccb5a7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/LongId.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/IdLong.java @@ -15,7 +15,7 @@ * 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.sta.model.id; +package de.fraunhofer.iosb.ilt.sta.model.core; import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; import java.util.Objects; @@ -24,21 +24,21 @@ * * @author jab */ -public class LongId implements Id { +public class IdLong implements Id { private Long value; - public LongId(Long value) { + public IdLong(Long value) { this.value = value; } - public LongId(int value) { + public IdLong(int value) { this.value = Long.valueOf(value); } @Override public BasicPersistenceType getBasicPersistenceType() { - return BasicPersistenceType.Integer; + return BasicPersistenceType.INTEGER; } @Override @@ -52,10 +52,13 @@ public void fromBasicPersitenceType(Object data) { } @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.value); - return hash; + public Long getValue() { + return value; + } + + @Override + public String getUrl() { + return toString(); } @Override @@ -69,29 +72,18 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - final LongId other = (LongId) obj; - if (!Objects.equals(this.value, other.value)) { - return false; - } - return true; + final IdLong other = (IdLong) obj; + return Objects.equals(this.value, other.value); } @Override - public Object getValue() { - return value; - } - - @Override - public String getUrl() { - return toString(); + public int hashCode() { + return Objects.hash(value); } @Override public String toString() { - if (value == null) { - return "null"; - } - return value.toString(); + return Objects.toString(getValue()); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/StringId.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/IdString.java similarity index 74% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/StringId.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/IdString.java index 552dd2a31..6f2b6a006 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/id/StringId.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/IdString.java @@ -15,26 +15,27 @@ * 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.sta.model.id; +package de.fraunhofer.iosb.ilt.sta.model.core; import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; +import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; import java.util.Objects; /** * * @author jab */ -public class StringId implements Id { +public class IdString implements Id { private String value; - public StringId(String value) { + public IdString(String value) { this.value = value; } @Override public BasicPersistenceType getBasicPersistenceType() { - return BasicPersistenceType.String; + return BasicPersistenceType.STRING; } @Override @@ -48,10 +49,15 @@ public void fromBasicPersitenceType(Object data) { } @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.value); - return hash; + public String getValue() { + return value; + } + + @Override + public String getUrl() { + return "'" + + UrlHelper.urlEncode(UrlHelper.escapeForStringConstant(value), true) + + "'"; } @Override @@ -65,29 +71,18 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - final StringId other = (StringId) obj; - if (!Objects.equals(this.value, other.value)) { - return false; - } - return true; + final IdString other = (IdString) obj; + return Objects.equals(this.value, other.value); } @Override - public Object getValue() { - return value; - } - - @Override - public String getUrl() { - return "'" + value + "'"; + public int hashCode() { + return Objects.hash(value); } @Override public String toString() { - if (value == null) { - return "null"; - } - return value; + return Objects.toString(getValue()); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/NamedDsHoldingEntity.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/NamedDsHoldingEntity.java new file mode 100644 index 000000000..ab6f9a91c --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/NamedDsHoldingEntity.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 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.sta.model.core; + +import de.fraunhofer.iosb.ilt.sta.model.*; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import java.util.Objects; + +/** + * A named entity that also a list of Datastreams and a list of + * MultiDatastreams. + * + * @author jab, scf + */ +public abstract class NamedDsHoldingEntity extends NamedEntity { + + private EntitySet datastreams; // 0..* + private EntitySet multiDatastreams; // 0..* + + public NamedDsHoldingEntity() { + this(null); + } + + public NamedDsHoldingEntity(Id id) { + super(id); + this.datastreams = new EntitySetImpl<>(EntityType.DATASTREAM); + this.multiDatastreams = new EntitySetImpl<>(EntityType.MULTIDATASTREAM); + } + + @Override + public EntityType getEntityType() { + return EntityType.THING; + } + + public EntitySet getDatastreams() { + return datastreams; + } + + public void setDatastreams(EntitySet datastreams) { + this.datastreams = datastreams; + } + + public EntitySet getMultiDatastreams() { + return multiDatastreams; + } + + public void setMultiDatastreams(EntitySet multiDatastreams) { + this.multiDatastreams = multiDatastreams; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), datastreams, multiDatastreams); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final NamedDsHoldingEntity other = (NamedDsHoldingEntity) obj; + return super.equals(other) + && Objects.equals(datastreams, other.datastreams) + && Objects.equals(multiDatastreams, other.multiDatastreams); + } +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/NamedEntity.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/NamedEntity.java new file mode 100644 index 000000000..2c94415fd --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/core/NamedEntity.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 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.sta.model.core; + +import java.util.Map; +import java.util.Objects; + +/** + * An abstract entity with: name, description and properties. + * + * @author jab, scf + */ +public abstract class NamedEntity extends AbstractEntity { + + private String name; + private String description; + private Map properties; + + private boolean setName; + private boolean setDescription; + private boolean setProperties; + + public NamedEntity(Id id) { + super(id); + } + + @Override + public void setEntityPropertiesSet() { + setName = true; + setDescription = true; + setProperties = true; + } + + /** + * @return the Name of the entity. + */ + public String getName() { + return name; + } + + /** + * Set the Name of the entity. + * + * @param name The Name to set. + */ + public void setName(String name) { + this.name = name; + setName = name != null; + } + + /** + * @return Flag indicating the Name was set by the user. + */ + public boolean isSetName() { + return setName; + } + + /** + * @return the Description of the entity. + */ + public String getDescription() { + return description; + } + + /** + * Set the Description of the entity. + * + * @param description The Description to set. + */ + public void setDescription(String description) { + this.description = description; + setDescription = description != null; + } + + /** + * @return Flag indicating the Description was set by the user. + */ + public boolean isSetDescription() { + return setDescription; + } + + /** + * @return the Properties map of the entity. + */ + public Map getProperties() { + return properties; + } + + /** + * Set the Properties map of the entity. + * + * @param properties The Properties to set. Setting this to an empty map + * will set the properties to null. + */ + public void setProperties(Map properties) { + if (properties != null && properties.isEmpty()) { + properties = null; + } + this.properties = properties; + setProperties = true; + } + + /** + * @return Flag indicating Properties was set by the user. + */ + public boolean isSetProperties() { + return setProperties; + } + + @Override + public int hashCode() { + return Objects.hash(name, description, properties); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final NamedEntity other = (NamedEntity) obj; + return super.equals(other) + && Objects.equals(this.name, other.name) + && Objects.equals(this.description, other.description) + && Objects.equals(this.properties, other.properties); + } +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/ObservationType.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/ObservationType.java new file mode 100644 index 000000000..df278a7aa --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/ObservationType.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 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.sta.model.ext; + +/** + * + * @author scf + */ +public enum ObservationType { + COMPLEX_OBSERVATION("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_ComplexObservation"); + + public final String code; + + private ObservationType(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInstant.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInstant.java index c6b88594c..bcd2013f8 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInstant.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInstant.java @@ -18,7 +18,7 @@ package de.fraunhofer.iosb.ilt.sta.model.ext; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import de.fraunhofer.iosb.ilt.sta.deserialize.TimeInstantDeserializer; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.TimeInstantDeserializer; import java.util.Objects; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -52,9 +52,7 @@ public static TimeInstant now(DateTimeZone timeZone) { @Override public int hashCode() { - int hash = 7; - hash = 37 * hash + Objects.hashCode(this.dateTime); - return hash; + return Objects.hash(dateTime); } @Override @@ -72,13 +70,10 @@ public boolean equals(Object obj) { if (this.dateTime == null && other.dateTime == null) { return true; } - if (this.dateTime == null | other.dateTime == null) { + if (this.dateTime == null || other.dateTime == null) { return false; } - if (!this.dateTime.isEqual(other.dateTime)) { - return false; - } - return true; + return this.dateTime.isEqual(other.dateTime); } public static TimeInstant parse(String value) { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInterval.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInterval.java index 38d35c482..e8363f492 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInterval.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeInterval.java @@ -18,7 +18,7 @@ package de.fraunhofer.iosb.ilt.sta.model.ext; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import de.fraunhofer.iosb.ilt.sta.deserialize.TimeIntervalDeserializer; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.TimeIntervalDeserializer; import java.util.Objects; import org.joda.time.DateTimeZone; import org.joda.time.Interval; @@ -45,9 +45,7 @@ private TimeInterval(Interval interval) { @Override public int hashCode() { - int hash = 5; - hash = 67 * hash + Objects.hashCode(this.interval); - return hash; + return Objects.hash(interval); } @Override @@ -62,10 +60,7 @@ public boolean equals(Object obj) { return false; } final TimeInterval other = (TimeInterval) obj; - if (!Objects.equals(this.interval, other.interval)) { - return false; - } - return true; + return Objects.equals(this.interval, other.interval); } public static TimeInterval create(long start, long end) { @@ -88,11 +83,11 @@ public Interval getInterval() { public String asISO8601() { DateTimeFormatter printer = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC); printer = printer.withChronology(interval.getChronology()); - StringBuffer buf = new StringBuffer(48); - printer.printTo(buf, interval.getStartMillis()); - buf.append('/'); - printer.printTo(buf, interval.getEndMillis()); - return buf.toString(); + StringBuilder timeString = new StringBuilder(48); + printer.printTo(timeString, interval.getStartMillis()); + timeString.append('/'); + printer.printTo(timeString, interval.getEndMillis()); + return timeString.toString(); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeValue.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeValue.java index 872b0ac07..ace0e16b7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeValue.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/TimeValue.java @@ -19,8 +19,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import de.fraunhofer.iosb.ilt.sta.deserialize.TimeValueDeserializer; -import de.fraunhofer.iosb.ilt.sta.serialize.TimeValueSerializer; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.TimeValueDeserializer; +import de.fraunhofer.iosb.ilt.sta.json.serialize.TimeValueSerializer; /** * Common interface for time values. Needed as STA sometimes does not scpecify wether an instant or an interval will be diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/UnitOfMeasurement.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/UnitOfMeasurement.java index ced8ea377..a4b143008 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/UnitOfMeasurement.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/ext/UnitOfMeasurement.java @@ -17,11 +17,9 @@ */ package de.fraunhofer.iosb.ilt.sta.model.ext; -import de.fraunhofer.iosb.ilt.sta.serialize.EntityFormatter; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; import java.io.IOException; import java.util.Objects; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Model class for UnitOfMeasurement. This is not a first class entity in STA. @@ -92,7 +90,7 @@ public void setDefinition(String definition) { @Override public String toString() { try { - return new EntityFormatter().writeObject(this); + return EntityFormatter.writeObject(this); } catch (IOException ex) { return this.toString(); } @@ -100,11 +98,7 @@ public String toString() { @Override public int hashCode() { - int hash = 5; - hash = 83 * hash + Objects.hashCode(this.name); - hash = 83 * hash + Objects.hashCode(this.symbol); - hash = 83 * hash + Objects.hashCode(this.definition); - return hash; + return Objects.hash(name,symbol,definition); } @Override @@ -125,9 +119,6 @@ public boolean equals(Object obj) { if (!Objects.equals(this.symbol, other.symbol)) { return false; } - if (!Objects.equals(this.definition, other.definition)) { - return false; - } - return true; + return Objects.equals(this.definition, other.definition); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/AbstractDatastreamMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/AbstractDatastreamMixIn.java new file mode 100644 index 000000000..8fef1424c --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/AbstractDatastreamMixIn.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 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.sta.model.mixin; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; + +/** + * MixIn to ensure that unitOfMeasurement is always included like stated in the + * standard (p28, Table 8-9) + * + * @author jab + */ +public interface AbstractDatastreamMixIn { + + @JsonIgnore + public abstract EntityType getEntityType(); + + @JsonIgnore + public abstract boolean isSetName(); + + @JsonIgnore + public abstract boolean isSetDescription(); + + @JsonIgnore + public void setObservationTypeIntern(String observationType); + + @JsonIgnore + public abstract boolean isSetObservationType(); + + @JsonIgnore + public abstract boolean isSetObservedArea(); + + @JsonIgnore + public abstract boolean isSetPhenomenonTime(); + + @JsonIgnore + public abstract boolean isSetProperties(); + + @JsonIgnore + public abstract boolean isSetResultTime(); + + @JsonIgnore + public abstract boolean isSetSensor(); + + @JsonIgnore + public abstract boolean isSetThing(); +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/DatastreamMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/DatastreamMixIn.java index 5ff324780..181016c9b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/DatastreamMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/DatastreamMixIn.java @@ -18,9 +18,6 @@ package de.fraunhofer.iosb.ilt.sta.model.mixin; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; /** * MixIn to ensure that unitOfMeasurement is always included like stated in the @@ -28,44 +25,11 @@ * * @author jab */ -public abstract class DatastreamMixIn { - - @JsonSerialize(include = JsonSerialize.Inclusion.ALWAYS) - public abstract UnitOfMeasurement getUnitOfMeasurement(); - - @JsonIgnore - public abstract EntityType getEntityType(); - - @JsonIgnore - public abstract boolean isSetName(); - - @JsonIgnore - public abstract boolean isSetDescription(); - - @JsonIgnore - public abstract boolean isSetObservationType(); - - @JsonIgnore - public abstract boolean isSetObservedArea(); - - @JsonIgnore - public abstract boolean isSetPhenomenonTime(); - - @JsonIgnore - public abstract boolean isSetProperties(); - - @JsonIgnore - public abstract boolean isSetResultTime(); - - @JsonIgnore - public abstract boolean isSetSensor(); +public interface DatastreamMixIn extends AbstractDatastreamMixIn { @JsonIgnore public abstract boolean isSetObservedProperty(); - @JsonIgnore - public abstract boolean isSetThing(); - @JsonIgnore public abstract boolean isSetUnitOfMeasurement(); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/EntitySetResultMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/EntitySetResultMixIn.java index c8f133db9..6fbb3c53b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/EntitySetResultMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/EntitySetResultMixIn.java @@ -24,7 +24,7 @@ * * @author jab */ -public abstract class EntitySetResultMixIn { +public interface EntitySetResultMixIn { @JsonProperty("value") public abstract EntitySet getValues(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/custom/geojson/FeatureMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/FeatureMixIn.java similarity index 92% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/custom/geojson/FeatureMixIn.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/FeatureMixIn.java index d1a627b14..2760d5d07 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/custom/geojson/FeatureMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/FeatureMixIn.java @@ -15,7 +15,7 @@ * 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.sta.model.custom.geojson; +package de.fraunhofer.iosb.ilt.sta.model.mixin; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -27,7 +27,7 @@ * * @author jab */ -public abstract class FeatureMixIn { +public interface FeatureMixIn { @JsonInclude(Include.NON_EMPTY) public abstract Map getProperties(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/FeatureOfInterestMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/FeatureOfInterestMixIn.java index fcd4cbbbc..69a1a4c60 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/FeatureOfInterestMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/FeatureOfInterestMixIn.java @@ -18,14 +18,14 @@ package de.fraunhofer.iosb.ilt.sta.model.mixin; import com.fasterxml.jackson.annotation.JsonIgnore; +import de.fraunhofer.iosb.ilt.sta.json.serialize.custom.CustomSerialization; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.serialize.custom.CustomSerialization; /** * * @author jab */ -public abstract class FeatureOfInterestMixIn { +public interface FeatureOfInterestMixIn { @CustomSerialization public abstract Object getFeature(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/HistoricalLocationMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/HistoricalLocationMixIn.java index 2dcd27403..e44bb10a0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/HistoricalLocationMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/HistoricalLocationMixIn.java @@ -18,14 +18,13 @@ package de.fraunhofer.iosb.ilt.sta.model.mixin; import com.fasterxml.jackson.annotation.JsonIgnore; - import de.fraunhofer.iosb.ilt.sta.path.EntityType; /** * * @author jab */ -public abstract class HistoricalLocationMixIn { +public interface HistoricalLocationMixIn { @JsonIgnore public abstract EntityType getEntityType(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/LocationMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/LocationMixIn.java index 499894e05..c00ca99bd 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/LocationMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/LocationMixIn.java @@ -18,14 +18,14 @@ package de.fraunhofer.iosb.ilt.sta.model.mixin; import com.fasterxml.jackson.annotation.JsonIgnore; +import de.fraunhofer.iosb.ilt.sta.json.serialize.custom.CustomSerialization; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.serialize.custom.CustomSerialization; /** * * @author jab */ -public abstract class LocationMixIn { +public interface LocationMixIn { @JsonIgnore public abstract EntityType getEntityType(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/MixinUtils.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/MixinUtils.java new file mode 100644 index 000000000..6c57b18d7 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/MixinUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 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.sta.model.mixin; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; +import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; +import de.fraunhofer.iosb.ilt.sta.model.Location; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; +import de.fraunhofer.iosb.ilt.sta.model.Sensor; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.ext.EntitySetResult; +import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; + +/** + * + * @author scf + */ +public class MixinUtils { + + private MixinUtils() { + // Utility class, not to be instantiated. + } + + public static void addMixins(ObjectMapper mapper) { + mapper.addMixIn(Datastream.class, DatastreamMixIn.class); + mapper.addMixIn(MultiDatastream.class, MultiDatastreamMixIn.class); + mapper.addMixIn(FeatureOfInterest.class, FeatureOfInterestMixIn.class); + mapper.addMixIn(HistoricalLocation.class, HistoricalLocationMixIn.class); + mapper.addMixIn(Location.class, LocationMixIn.class); + mapper.addMixIn(Observation.class, ObservationMixIn.class); + mapper.addMixIn(ObservedProperty.class, ObservedPropertyMixIn.class); + mapper.addMixIn(Sensor.class, SensorMixIn.class); + mapper.addMixIn(Thing.class, ThingMixIn.class); + mapper.addMixIn(UnitOfMeasurement.class, UnitOfMeasurementMixIn.class); + mapper.addMixIn(EntitySetResult.class, EntitySetResultMixIn.class); + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/MultiDatastreamMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/MultiDatastreamMixIn.java index 1d523d7bb..82e6b6f14 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/MultiDatastreamMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/MultiDatastreamMixIn.java @@ -18,7 +18,6 @@ package de.fraunhofer.iosb.ilt.sta.model.mixin; import com.fasterxml.jackson.annotation.JsonIgnore; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; /** * MixIn to ensure that unitOfMeasurement is always included like stated in the @@ -26,44 +25,14 @@ * * @author jab */ -public abstract class MultiDatastreamMixIn { - - @JsonIgnore - public abstract EntityType getEntityType(); - - @JsonIgnore - public abstract boolean isSetName(); - - @JsonIgnore - public abstract boolean isSetDescription(); - - @JsonIgnore - public abstract boolean isSetObservationType(); +public interface MultiDatastreamMixIn extends AbstractDatastreamMixIn { @JsonIgnore public abstract boolean isSetMultiObservationDataTypes(); - @JsonIgnore - public abstract boolean isSetObservedArea(); - - @JsonIgnore - public abstract boolean isSetPhenomenonTime(); - - @JsonIgnore - public abstract boolean isSetProperties(); - - @JsonIgnore - public abstract boolean isSetResultTime(); - - @JsonIgnore - public abstract boolean isSetSensor(); - @JsonIgnore public abstract boolean isSetObservedProperties(); - @JsonIgnore - public abstract boolean isSetThing(); - @JsonIgnore public abstract boolean isSetUnitOfMeasurements(); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservationMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservationMixIn.java index d91b3059b..e4b30ea06 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservationMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservationMixIn.java @@ -18,13 +18,17 @@ package de.fraunhofer.iosb.ilt.sta.model.mixin; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import de.fraunhofer.iosb.ilt.sta.path.EntityType; /** * * @author jab */ -public abstract class ObservationMixIn { +public interface ObservationMixIn { + + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public abstract Object getResult(); @JsonIgnore public abstract EntityType getEntityType(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservedPropertyMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservedPropertyMixIn.java index 2fbe78aff..249acedc4 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservedPropertyMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ObservedPropertyMixIn.java @@ -24,7 +24,7 @@ * * @author jab */ -public abstract class ObservedPropertyMixIn { +public interface ObservedPropertyMixIn { @JsonIgnore public abstract EntityType getEntityType(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/SensorMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/SensorMixIn.java index f87e0e871..49a3ad9e5 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/SensorMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/SensorMixIn.java @@ -24,7 +24,7 @@ * * @author jab */ -public abstract class SensorMixIn { +public interface SensorMixIn { @JsonIgnore public abstract EntityType getEntityType(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ThingMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ThingMixIn.java index b1978f243..9c71094be 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ThingMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/ThingMixIn.java @@ -24,7 +24,7 @@ * * @author jab */ -public abstract class ThingMixIn { +public interface ThingMixIn { @JsonIgnore public abstract EntityType getEntityType(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/UnitOfMeasurementMixIn.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/UnitOfMeasurementMixIn.java index 48644055e..eb8dbf4e2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/UnitOfMeasurementMixIn.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/model/mixin/UnitOfMeasurementMixIn.java @@ -17,14 +17,15 @@ */ package de.fraunhofer.iosb.ilt.sta.model.mixin; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.annotation.JsonInclude; /** - * MixIn to ensure that unitOfMeasurement is always included like stated in the standard (p28, Table 8-9) + * MixIn to ensure that fields of the unitOfMeasurement is always included, even + * if null, as stated in the standard. (p28, Table 8-9) * * @author jab */ -@JsonSerialize(include = JsonSerialize.Inclusion.ALWAYS) -public abstract class UnitOfMeasurementMixIn { +@JsonInclude(JsonInclude.Include.ALWAYS) +public interface UnitOfMeasurementMixIn { } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttManager.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttManager.java index a44d189db..177c87069 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttManager.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttManager.java @@ -1,21 +1,24 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.messagebus.MessageListener; import de.fraunhofer.iosb.ilt.sta.model.Observation; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; import de.fraunhofer.iosb.ilt.sta.mqtt.create.EntityCreateListener; @@ -25,8 +28,7 @@ import de.fraunhofer.iosb.ilt.sta.mqtt.subscription.SubscriptionFactory; import de.fraunhofer.iosb.ilt.sta.mqtt.subscription.SubscriptionListener; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.persistence.EntityChangeListener; -import de.fraunhofer.iosb.ilt.sta.persistence.EntityChangedEvent; +import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; import de.fraunhofer.iosb.ilt.sta.service.RequestType; @@ -36,10 +38,11 @@ import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; import de.fraunhofer.iosb.ilt.sta.settings.MqttSettings; import de.fraunhofer.iosb.ilt.sta.util.ProcessorHelper; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import java.io.IOException; -import java.nio.charset.Charset; import java.util.EnumMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; @@ -53,10 +56,9 @@ * * @author jab */ -public class MqttManager implements SubscriptionListener, EntityChangeListener, EntityCreateListener { +public class MqttManager implements SubscriptionListener, MessageListener, EntityCreateListener { private static MqttManager instance; - private static final Charset ENCODING = Charset.forName("UTF-8"); private static final Logger LOGGER = LoggerFactory.getLogger(MqttManager.class); public static synchronized void init(CoreSettings settings) { @@ -81,7 +83,7 @@ public static MqttManager getInstance() { private final Map> subscriptions = new EnumMap<>(EntityType.class); private final CoreSettings settings; private MqttServer server; - private BlockingQueue entityChangedEventQueue; + private BlockingQueue entityChangedEventQueue; private ExecutorService entityChangedExecutorService; private BlockingQueue observationCreateEventQueue; private ExecutorService observationCreateExecutorService; @@ -101,6 +103,7 @@ private MqttManager(CoreSettings settings) { private void init() { MqttSettings mqttSettings = settings.getMqttSettings(); + SubscriptionFactory.init(settings); if (mqttSettings.isEnableMqtt()) { enabledMqtt = true; shutdown = false; @@ -109,14 +112,14 @@ private void init() { entityChangedExecutorService = ProcessorHelper.createProcessors( mqttSettings.getSubscribeThreadPoolSize(), entityChangedEventQueue, - x -> handleEntityChangedEvent(x), + this::handleEntityChangedEvent, "MqttManager EntityChangedEventProcessor"); // start watching for ObservationCreateEvents observationCreateEventQueue = new ArrayBlockingQueue<>(mqttSettings.getCreateMessageQueueSize()); observationCreateExecutorService = ProcessorHelper.createProcessors( mqttSettings.getCreateThreadPoolSize(), observationCreateEventQueue, - x -> handleObservationCreateEvent(x), + this::handleObservationCreateEvent, "MqttManager ObservationCreateEventProcessor"); // start MQTT server server = MqttServerFactory.getInstance().get(settings); @@ -132,8 +135,6 @@ private void init() { observationCreateEventQueue = new ArrayBlockingQueue<>(1); server = null; } - - SubscriptionFactory.init(settings); } private void doShutdown() { @@ -145,23 +146,25 @@ private void doShutdown() { } } - private void handleEntityChangedEvent(EntityChangedEvent e) { + private void handleEntityChangedEvent(EntityChangedMessage message) { + if (message.getEventType() == EntityChangedMessage.Type.DELETE) { + // v1.0 does not do delete notification. + return; + } // check if there is any subscription, if not do not publish at all - if (!subscriptions.containsKey(e.getNewEntity().getEntityType())) { + EntityType entityType = message.getEntityType(); + if (!subscriptions.containsKey(entityType)) { return; } PersistenceManager persistenceManager = PersistenceManagerFactory.getInstance().create(); + // Send a complete entity through the bus, or just an entity-id? + Entity entity = message.getEntity(); + Set fields = message.getFields(); try { // for each subscription on EntityType check match - for (Subscription subscription : subscriptions.get(e.getNewEntity().getEntityType()).keySet()) { - if (subscription.matches(persistenceManager, e.getOldEntity(), e.getNewEntity())) { - Entity realEntity = persistenceManager.getEntityById(settings.getServiceRootUrl(), e.getNewEntity().getEntityType(), e.getNewEntity().getId()); - try { - String payload = subscription.formatMessage(realEntity); - server.publish(subscription.getTopic(), payload.getBytes(ENCODING), settings.getMqttSettings().getQosLevel()); - } catch (IOException ex) { - LOGGER.error("publishing to MQTT on topic '" + subscription.getTopic() + "' failed", ex); - } + for (Subscription subscription : subscriptions.get(entityType).keySet()) { + if (subscription.matches(persistenceManager, entity, fields)) { + notifySubscription(subscription, entity); } } } catch (Exception ex) { @@ -169,41 +172,54 @@ private void handleEntityChangedEvent(EntityChangedEvent e) { } finally { persistenceManager.close(); } - return; + } + + private void notifySubscription(Subscription subscription, Entity entity) { + try { + String payload = subscription.formatMessage(entity); + server.publish(subscription.getTopic(), payload.getBytes(StringHelper.ENCODING), settings.getMqttSettings().getQosLevel()); + } catch (IOException ex) { + LOGGER.error("publishing to MQTT on topic '" + subscription.getTopic() + "' failed", ex); + } } private void handleObservationCreateEvent(ObservationCreateEvent e) { // check path? - if (!e.getTopic().endsWith("Observations")) { - LOGGER.info("received message on topic '{}' which is no valid topic to create an observation."); + String topic = e.getTopic(); + if (!topic.endsWith("Observations")) { + LOGGER.info("received message on topic '{}' which is no valid topic to create an observation.", topic); return; } - String url = e.getTopic().replaceFirst(settings.getApiVersion(), ""); + String url = topic.replaceFirst(settings.getApiVersion(), ""); ServiceResponse response = new Service(settings).execute(new ServiceRequestBuilder() - .withRequestType(RequestType.Create) + .withRequestType(RequestType.CREATE) .withContent(e.getPayload()) .withUrlPath(url) .build()); if (response.isSuccessful()) { - LOGGER.info("Observation (ID {}) created via MQTT", response.getResult().getId().getValue()); + LOGGER.debug("Observation (ID {}) created via MQTT", response.getResult().getId().getValue()); } else { LOGGER.error("Creating observation via MQTT failed (topic: {}, payload: {}, code: {}, message: {})", - e.getTopic(), e.getPayload(), response.getCode(), response.getMessage()); + topic, e.getPayload(), response.getCode(), response.getMessage()); } } - private void entityChanged(EntityChangedEvent e) { + private void entityChanged(EntityChangedMessage e) { if (shutdown || !enabledMqtt) { return; } if (!entityChangedEventQueue.offer(e)) { - LOGGER.warn("EntityChangedevent discarded because message queue is full {}! Increase mqtt.CreateMessageQueueSize and/or mqtt.CreateThreadPoolSize.", entityChangedEventQueue.size()); + LOGGER.warn("EntityChangedevent discarded because message queue is full {}! Increase mqtt.SubscribeMessageQueueSize and/or mqtt.SubscribeThreadPoolSize.", entityChangedEventQueue.size()); } } @Override public void onSubscribe(SubscriptionEvent e) { Subscription subscription = SubscriptionFactory.getInstance().get(e.getTopic()); + if (subscription == null) { + // Not a valid topic. + return; + } Map subscriptionsMap = subscriptions.get(subscription.getEntityType()); synchronized (subscriptionsMap) { @@ -222,6 +238,10 @@ public void onSubscribe(SubscriptionEvent e) { @Override public void onUnsubscribe(SubscriptionEvent e) { Subscription subscription = SubscriptionFactory.getInstance().get(e.getTopic()); + if (subscription == null) { + // Not a valid topic. + return; + } final Map subscriptionsMap = subscriptions.get(subscription.getEntityType()); synchronized (subscriptionsMap) { AtomicInteger clientCount = subscriptionsMap.get(subscription); @@ -230,25 +250,15 @@ public void onUnsubscribe(SubscriptionEvent e) { LOGGER.debug("Now {} subscriptions for topic {}.", newCount, subscription.getTopic()); if (newCount <= 0) { subscriptionsMap.remove(subscription); - LOGGER.debug("Removed subscription for topic {}.", newCount, subscription.getTopic()); + LOGGER.debug("Removed last subscription for topic {}.", subscription.getTopic()); } } } } @Override - public void entityInserted(EntityChangedEvent e) { - entityChanged(e); - } - - @Override - public void entityDeleted(EntityChangedEvent e) { - - } - - @Override - public void entityUpdated(EntityChangedEvent e) { - entityChanged(e); + public void messageReceived(EntityChangedMessage message) { + entityChanged(message); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServer.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServer.java index 693f29892..21e4a60a2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServer.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServer.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServerFactory.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServerFactory.java index 8898f9c59..a7c113233 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServerFactory.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/MqttServerFactory.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/EntityCreateListener.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/EntityCreateListener.java index 6e6b96c37..b498a325c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/EntityCreateListener.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/EntityCreateListener.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.create; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/ObservationCreateEvent.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/ObservationCreateEvent.java index 43c200bc7..0226854b7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/ObservationCreateEvent.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/create/ObservationCreateEvent.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.create; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/AbstractSubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/AbstractSubscription.java new file mode 100644 index 000000000..83dc7c39b --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/AbstractSubscription.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2016 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.sta.mqtt.subscription; + +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; +import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; +import de.fraunhofer.iosb.ilt.sta.query.expression.Path; +import de.fraunhofer.iosb.ilt.sta.query.expression.constant.IntegerConstant; +import de.fraunhofer.iosb.ilt.sta.query.expression.constant.StringConstant; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.comparison.Equal; +import de.fraunhofer.iosb.ilt.sta.util.PathHelper; +import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * + * @author jab + */ +public abstract class AbstractSubscription implements Subscription { + + private static Map> navigationProperties = null; + + protected final String topic; + protected EntityType entityType; + protected Expression matchExpression = null; + private Predicate matcher; + protected ResourcePath path; + protected String serviceRootUrl; + + public AbstractSubscription(String topic, ResourcePath path, String serviceRootUrl) { + initNavigationProperties(); + this.topic = topic; + this.path = path; + this.serviceRootUrl = serviceRootUrl; + } + + private static void initNavigationProperties() { + if (navigationProperties == null) { + navigationProperties = new EnumMap<>(EntityType.class); + for (EntityType type : EntityType.values()) { + navigationProperties.put(type, + type.getPropertySet().stream() + .filter(x -> x instanceof NavigationProperty) + .map(x -> (NavigationProperty) x) + .collect(Collectors.toList())); + } + } + } + + @Override + public boolean matches(PersistenceManager persistenceManager, Entity newEntity, Set fields) { + if (!newEntity.getEntityType().equals(entityType)) { + return false; + } + if (matcher != null && !matcher.test(newEntity)) { + return false; + } + if (matchExpression != null) { + Query query = new Query(); + query.setFilter(matchExpression); + Object result = persistenceManager.get(newEntity.getPath(), query); + return result != null; + } + return true; + } + + protected void generateFilter(int pathElementOffset) { + EntityType lastType = getEntityType(); + List properties = new ArrayList<>(); + for (int i = path.size() - 1 - pathElementOffset; i >= 0; i--) { + ResourcePathElement element = path.get(i); + if (!(element instanceof EntityPathElement)) { + continue; + } + final EntityPathElement epe = (EntityPathElement) element; + final NavigationProperty navProp = PathHelper.getNavigationProperty(lastType, epe.getEntityType()); + + Id id = epe.getId(); + if (!navProp.isSet && id != null) { + createMatcher(navProp, id); + assert (i <= 1); + return; + } + + properties.add(navProp); + lastType = epe.getEntityType(); + + if (id != null) { + createMatchExpression(properties, epe); + // there should be at most two PathElements left, the EntitySetPath and the EntityPath now visiting + assert (i <= 1); + return; + } + } + } + + private void createMatcher(final NavigationProperty navProp, Id id) { + // We have a collectionSubscription of type one-to-many. + // Create a (cheap) matcher instead of an (expensive) Expression + matcher = (Entity t) -> { + Entity parent = (Entity) t.getProperty(navProp); + if (parent == null) { + // can be for Observation->Datastream when Observation is MultiDatastream. + return false; + } + return id.equals(parent.getId()); + }; + } + + private void createMatchExpression(List properties, final EntityPathElement epe) { + properties.add(EntityProperty.ID); + String epeId = epe.getId().getUrl(); + if (epeId.startsWith("'")) { + matchExpression = new Equal(new Path(properties), new StringConstant(epeId.substring(1, epeId.length() - 1))); + } else { + matchExpression = new Equal(new Path(properties), new IntegerConstant(epeId)); + } + } + + @Override + public EntityType getEntityType() { + return entityType; + } + + @Override + public String getTopic() { + return topic; + } + + @Override + public String formatMessage(Entity entity) throws IOException { + entity.setSelfLink(UrlHelper.generateSelfLink(path, entity)); + for (NavigationProperty navigationProperty : navigationProperties.get(entity.getEntityType())) { + if (navigationProperty.isSet) { + EntitySet property = (EntitySet) entity.getProperty(navigationProperty); + property.setNavigationLink(UrlHelper.generateNavLink(path, entity, property, true)); + } else { + Entity property = (Entity) entity.getProperty(navigationProperty); + if (property != null) { + property.setNavigationLink(UrlHelper.generateNavLink(path, entity, property, true)); + } + } + } + return doFormatMessage(entity); + } + + public abstract String doFormatMessage(Entity entity) throws IOException; + + @Override + public int hashCode() { + return Objects.hash(topic, entityType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AbstractSubscription other = (AbstractSubscription) obj; + if (!Objects.equals(this.topic, other.topic)) { + return false; + } + return this.entityType == other.entityType; + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySetSubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySetSubscription.java index 7d751b017..fc0788a10 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySetSubscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySetSubscription.java @@ -1,35 +1,37 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.subscription; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import static de.fraunhofer.iosb.ilt.sta.mqtt.subscription.Subscription.ENCODING; import de.fraunhofer.iosb.ilt.sta.parser.query.QueryParser; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.serialize.EntityFormatter; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,10 +39,10 @@ * * @author jab */ -public class EntitySetSubscription extends Subscription { +public class EntitySetSubscription extends AbstractSubscription { private static final Logger LOGGER = LoggerFactory.getLogger(EntitySetSubscription.class); - private final List selectedProperties = new ArrayList<>(); + private final Set selectedProperties = new HashSet<>(); public EntitySetSubscription(String topic, ResourcePath path, String serviceRootUrl) { super(topic, path, serviceRootUrl); @@ -70,14 +72,14 @@ private void init() { private Query parseQuery(String topic) { String queryString = null; try { - queryString = URLDecoder.decode(topic, ENCODING.name()); + queryString = URLDecoder.decode(topic, StringHelper.ENCODING.name()); } catch (UnsupportedEncodingException ex) { LOGGER.error("Unsupported encoding.", ex); } try { return QueryParser.parseQuery(queryString, new CoreSettings()); } catch (IllegalArgumentException e) { - LOGGER.error("Invalid query: " + e.getMessage()); + LOGGER.error("Invalid query: {} ERROR: {}", queryString, e.getMessage()); return null; } } @@ -85,8 +87,23 @@ private Query parseQuery(String topic) { @Override public String doFormatMessage(Entity entity) throws IOException { if (!selectedProperties.isEmpty()) { - return new EntityFormatter(selectedProperties).writeEntity(entity); + entity.setSelectedProperties(selectedProperties); } - return new EntityFormatter().writeEntity(entity); + return EntityFormatter.writeEntity(entity); } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), selectedProperties); + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) { + return false; + } + final EntitySetSubscription other = (EntitySetSubscription) obj; + return Objects.equals(this.selectedProperties, other.selectedProperties); + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySubscription.java index e3ca89d65..08c3930af 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySubscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/EntitySubscription.java @@ -1,39 +1,41 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.subscription; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; +import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; -import de.fraunhofer.iosb.ilt.sta.serialize.EntityFormatter; import java.io.IOException; +import java.util.Set; import java.util.function.Predicate; -import org.slf4j.LoggerFactory; /** * * @author jab */ -public class EntitySubscription extends Subscription { +public class EntitySubscription extends AbstractSubscription { - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(EntitySubscription.class); private Predicate matcher; public EntitySubscription(String topic, ResourcePath path, String serviceRootUrl) { @@ -46,26 +48,35 @@ private void init() { throw new IllegalArgumentException("Invalid subscription to: '" + topic + "': query options not allowed for subscription on an entitiy."); } entityType = ((EntityPathElement) path.getLastElement()).getEntityType(); - if (path.getPathElements().size() == 2 - && path.getPathElements().get(path.getPathElements().size() - 2) instanceof EntitySetPathElement) { - matcher = x -> x.getProperty(EntityProperty.Id).equals(((EntityPathElement) path.getLastElement()).getId()); + final int size = path.size(); + if (size == 2 && path.get(0) instanceof EntitySetPathElement) { + Id id = ((EntityPathElement) path.getLastElement()).getId(); + matcher = x -> x.getProperty(EntityProperty.ID).equals(id); } generateFilter(1); } @Override - public boolean matches(PersistenceManager persistenceManager, Entity oldEntity, Entity newEntity) { - if (matcher != null) { - if (!matcher.test(newEntity)) { - return false; - } + public boolean matches(PersistenceManager persistenceManager, Entity newEntity, Set fields) { + if (matcher != null && !matcher.test(newEntity)) { + return false; } - return super.matches(persistenceManager, oldEntity, newEntity); + return super.matches(persistenceManager, newEntity, fields); } @Override public String doFormatMessage(Entity entity) throws IOException { - return new EntityFormatter().writeEntity(entity); + return EntityFormatter.writeEntity(entity); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/PropertySubscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/PropertySubscription.java index 1e42210d0..5ed875ffc 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/PropertySubscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/PropertySubscription.java @@ -1,38 +1,42 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.subscription; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; -import de.fraunhofer.iosb.ilt.sta.serialize.EntityFormatter; import java.io.IOException; -import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; import java.util.function.Predicate; /** * * @author jab */ -public class PropertySubscription extends Subscription { +public class PropertySubscription extends AbstractSubscription { private Property property; private Predicate matcher; @@ -46,38 +50,55 @@ private void init() { if (!SubscriptionFactory.getQueryFromTopic(topic).isEmpty()) { throw new IllegalArgumentException("Invalid subscription to: '" + topic + "': query options not allowed for subscription on a property."); } - entityType = ((EntityPathElement) path.getPathElements().get(path.getPathElements().size() - 2)).getEntityType(); - property = ((PropertyPathElement) path.getPathElements().get(path.getPathElements().size() - 1)).getProperty(); + final int size = path.size(); + entityType = ((EntityPathElement) path.get(size - 2)).getEntityType(); + property = ((PropertyPathElement) path.get(size - 1)).getProperty(); if (path.getIdentifiedElement() != null) { - matcher = x -> x.getProperty(EntityProperty.Id).equals(path.getIdentifiedElement().getId()); + Id id = path.getIdentifiedElement().getId(); + matcher = x -> x.getProperty(EntityProperty.ID).equals(id); } generateFilter(2); } @Override - public boolean matches(PersistenceManager persistenceManager, Entity oldEntity, Entity newEntity) { - if (matcher != null) { - if (!matcher.test(newEntity)) { - return false; - } + public boolean matches(PersistenceManager persistenceManager, Entity newEntity, Set fields) { + if (matcher != null && !matcher.test(newEntity)) { + return false; } - if (oldEntity != null && newEntity != null) { - if (oldEntity.getProperty(property) == null - | newEntity.getProperty(property) == null) { - return false; - } - if (oldEntity.getProperty(property) != null - && newEntity.getProperty(property) != null - && oldEntity.getProperty(property).equals(newEntity.getProperty(property))) { - return false; - } + if (fields == null || !fields.contains(property)) { + return false; } - return super.matches(persistenceManager, oldEntity, newEntity); + return super.matches(persistenceManager, newEntity, fields); } @Override public String doFormatMessage(Entity entity) throws IOException { - return new EntityFormatter(Arrays.asList(property)).writeEntity(entity); + HashSet propNames = new HashSet<>(1); + propNames.add(property.getJsonName()); + entity.setSelectedPropertyNames(propNames); + return EntityFormatter.writeEntity(entity); } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), property); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PropertySubscription other = (PropertySubscription) obj; + return super.equals(obj) + && Objects.equals(this.property, other.property); + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/Subscription.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/Subscription.java index 60ccd4fb5..30e90ed2b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/Subscription.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/Subscription.java @@ -1,180 +1,66 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2018 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.subscription; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; import de.fraunhofer.iosb.ilt.sta.path.Property; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; -import de.fraunhofer.iosb.ilt.sta.query.expression.Path; -import de.fraunhofer.iosb.ilt.sta.query.expression.constant.IntegerConstant; -import de.fraunhofer.iosb.ilt.sta.query.expression.constant.StringConstant; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.comparison.Equal; -import de.fraunhofer.iosb.ilt.sta.util.PathHelper; -import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; import java.io.IOException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Set; /** * - * @author jab + * @author scf */ -public abstract class Subscription { - - private static Map> navigationProperties = null; - private static final Logger LOGGER = LoggerFactory.getLogger(Subscription.class); - // TODO make encoding global constant - protected static final Charset ENCODING = Charset.forName("UTF-8"); - protected final String topic; - protected EntityType entityType; - protected Expression matchExpression = null; - protected ResourcePath path; - protected String serviceRootUrl; - - public Subscription(String topic, ResourcePath path, String serviceRootUrl) { - initNavigationProperties(); - this.topic = topic; - this.path = path; - this.serviceRootUrl = serviceRootUrl; - } - - private void initNavigationProperties() { - if (navigationProperties == null) { - navigationProperties = new HashMap<>(); - for (EntityType entityType : EntityType.values()) { - navigationProperties.put(entityType, - entityType.getPropertySet().stream() - .filter(x -> x instanceof NavigationProperty) - .map(x -> (NavigationProperty) x) - .collect(Collectors.toList())); - } - } - } - - public boolean matches(PersistenceManager persistenceManager, Entity oldEntity, Entity newEntity) { - if (!newEntity.getEntityType().equals(entityType)) { - return false; - } - if (matchExpression != null) { - Query query = new Query(); - query.setFilter(matchExpression); - Object result = persistenceManager.get(newEntity.getPath(), query); - return result != null; - } - return true; - } - - protected void generateFilter(int pathElementOffset) { - EntityType lastType = getEntityType(); - List properties = new ArrayList<>(); - for (int i = path.getPathElements().size() - 1 - pathElementOffset; i >= 0; i--) { - - if (path.getPathElements().get(i) instanceof EntityPathElement) { - final EntityPathElement epe = (EntityPathElement) path.getPathElements().get(i); - final NavigationProperty navProp = PathHelper.getNavigationProperty(lastType, epe.getEntityType()); - properties.add(navProp); - lastType = epe.getEntityType(); - - if (epe.getId() != null) { - properties.add(EntityProperty.Id); - String epeId = epe.getId().getUrl(); - if (epeId.startsWith("'")) { - matchExpression = new Equal(new Path(properties), new StringConstant(epeId.substring(1, epeId.length() - 1))); - } else { - matchExpression = new Equal(new Path(properties), new IntegerConstant(epeId)); - } - // there should be at most two PathElements left, the EntitySetPath and the EntityPath now visiting - assert (i <= 1); - return; - } - } - - } - - } - - public EntityType getEntityType() { - return entityType; - } - - public String getTopic() { - return topic; - } - - public String formatMessage(Entity entity) throws IOException { - entity.setSelfLink(UrlHelper.generateSelfLink(path, entity)); - for (NavigationProperty navigationProperty : navigationProperties.get(entity.getEntityType())) { - if (navigationProperty.isSet) { - EntitySet property = (EntitySet) entity.getProperty(navigationProperty); - property.setNavigationLink(UrlHelper.generateNavLink(path, entity, property, true)); - } else { - Entity property = (Entity) entity.getProperty(navigationProperty); - if (property != null) { - property.setNavigationLink(UrlHelper.generateNavLink(path, entity, property, true)); - } - } - } - return doFormatMessage(entity); - } - - public abstract String doFormatMessage(Entity entity) throws IOException; - - @Override - public int hashCode() { - int hash = 7; - hash = 97 * hash + Objects.hashCode(this.topic); - hash = 97 * hash + Objects.hashCode(this.entityType); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Subscription other = (Subscription) obj; - if (!Objects.equals(this.topic, other.topic)) { - return false; - } - if (this.entityType != other.entityType) { - return false; - } - return true; - } +public interface Subscription { + + /** + * Format the given entity so it fits for the subscription. + * + * @param entity The entity to format. + * @return A message body. + * @throws IOException If the formatting failed. + */ + String formatMessage(Entity entity) throws IOException; + + /** + * Get the type of entity that is of interest for this Subscription. + * + * @return the type of entity that is of interest for this Subscription. + */ + EntityType getEntityType(); + + /** + * Get the topic of the Subscription. + * + * @return The topic of the Subscription. + */ + String getTopic(); + + /** + * Check of the given entity is of interest to this Subscription. + * + * @param persistenceManager The PersistenceManager to use for queries. + * @param newEntity The entity to check. + * @param fields The fields of the entity that changed. + * @return true if the change is of interest for the Subscription. + */ + boolean matches(PersistenceManager persistenceManager, Entity newEntity, Set fields); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionEvent.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionEvent.java index e22b75653..e2a28d7cc 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionEvent.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionEvent.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.subscription; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionFactory.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionFactory.java index 928c26cc9..c4af44e69 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionFactory.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionFactory.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.subscription; @@ -24,9 +25,9 @@ import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import java.nio.charset.Charset; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,10 +37,10 @@ */ public class SubscriptionFactory { + private static final String URI_PATH_SEP = "/"; private static SubscriptionFactory instance; - private static final Logger LOGGER = LoggerFactory.getLogger(Subscription.class); - // TODO make encoding global constant - private static final Charset ENCODING = Charset.forName("UTF-8"); + + private static final Logger LOGGER = LoggerFactory.getLogger(SubscriptionFactory.class); public static synchronized void init(CoreSettings settings) { if (instance == null) { @@ -56,17 +57,17 @@ public static synchronized SubscriptionFactory getInstance() { private static String getPathFromTopic(String topic) { String pathString = topic.contains("?") - ? topic.substring(0, topic.indexOf("?")) + ? topic.substring(0, topic.indexOf('?')) : topic; - if (!pathString.startsWith("/")) { - pathString = "/" + pathString; + if (!pathString.startsWith(URI_PATH_SEP)) { + pathString = URI_PATH_SEP + pathString; } return pathString; } public static String getQueryFromTopic(String topic) { return topic.contains("?") - ? topic.substring(topic.indexOf("?") + 1) + ? topic.substring(topic.indexOf('?') + 1) : ""; } private final CoreSettings settings; @@ -82,33 +83,34 @@ public Subscription get(String topic) { if (topic == null || topic.isEmpty()) { throw new IllegalArgumentException(errorMsg + "topic must be non-empty."); } - if (topic.startsWith("/")) { - throw new IllegalArgumentException(errorMsg + "topic must not start with '/'."); + if (topic.startsWith(URI_PATH_SEP)) { + throw new IllegalArgumentException(errorMsg + "topic must not start with '" + URI_PATH_SEP + "'."); } String internalTopic = topic; String topicPrefix = settings.getMqttSettings().getTopicPrefix(); if (topicPrefix != null && !topicPrefix.isEmpty()) { if (!topic.startsWith(topicPrefix)) { - // TODO maybe just ignore subscriptio here? - throw new IllegalArgumentException("Topic '" + topic + " does not start with expected prefix '" + topicPrefix + "'"); + LOGGER.info("Subscription for invalid topic: {}", topic); + return null; } internalTopic = topic.substring(topicPrefix.length()); } ResourcePath path = parsePath(getPathFromTopic(internalTopic)); - if (path == null || path.getPathElements().isEmpty()) { + if (path == null || path.isEmpty()) { throw new IllegalArgumentException(errorMsg + "invalid path."); } path.setServiceRootUrl(settings.getServiceRootUrl()); path.compress(); + final int size = path.size(); if (path.getLastElement() instanceof EntitySetPathElement) { // SensorThings Standard 14.2.1 - Subscribe to EntitySet return new EntitySetSubscription(topic, path, settings.getServiceRootUrl()); } else if (path.getLastElement() instanceof EntityPathElement) { // SensorThings Standard 14.2.2 - Subscribe to Entity return new EntitySubscription(topic, path, settings.getServiceRootUrl()); - } else if (path.getPathElements().size() >= 2 - && path.getPathElements().get(path.getPathElements().size() - 2) instanceof EntityPathElement - && path.getPathElements().get(path.getPathElements().size() - 1) instanceof PropertyPathElement) { + } else if (size >= 2 + && path.get(size - 2) instanceof EntityPathElement + && path.get(size - 1) instanceof PropertyPathElement) { // SensorThings Standard 14.2.3 - Subscribe to Property return new PropertySubscription(topic, path, settings.getServiceRootUrl()); @@ -121,14 +123,14 @@ public Subscription get(String topic) { private ResourcePath parsePath(String topic) { ResourcePath result = null; try { - String pathString = URLDecoder.decode(topic, ENCODING.name()); + String pathString = URLDecoder.decode(topic, StringHelper.ENCODING.name()); result = PathParser.parsePath(idManager, "", pathString); } catch (UnsupportedEncodingException ex) { LOGGER.error("Encoding not supported.", ex); } catch (NumberFormatException e) { LOGGER.error("Not a valid id."); } catch (IllegalStateException e) { - LOGGER.error("Not a valid path: " + e.getMessage()); + LOGGER.error("Not a valid path: {}", e.getMessage()); } return result; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionListener.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionListener.java index 5f779a69b..74cde36a3 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionListener.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/mqtt/subscription/SubscriptionListener.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.mqtt.subscription; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/DumpVisitor.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/DumpVisitor.java deleted file mode 100644 index b8ee80d40..000000000 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/DumpVisitor.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.parser.path; - -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DumpVisitor implements ParserVisitor { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(DumpVisitor.class); - private int indent = 0; - - private String indentString() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < indent; ++i) { - sb.append(" "); - } - return sb.toString(); - } - - public ResourcePath defltAction(SimpleNode node, ResourcePath data) { - if (node.value == null) { - LOGGER.info(indentString() + node); - } else { - LOGGER.info("{}{} : ({}){}", indentString(), node, node.value.getClass().getSimpleName(), node.value); - } - ++indent; - node.childrenAccept(this, data); - --indent; - return data; - } - - @Override - public ResourcePath visit(SimpleNode node, ResourcePath data) { - LOGGER.info("{}{}: acceptor not implemented in subclass?", indentString(), node); - ++indent; - node.childrenAccept(this, data); - --indent; - return data; - } - - @Override - public ResourcePath visit(ASTStart node, ResourcePath data) { - LOGGER.info(indentString() + node); - ++indent; - node.childrenAccept(this, data); - --indent; - return data; - } - - @Override - public ResourcePath visit(ASTIdentifiedPath node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeDatastream node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcDatastreams node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeMultiDatastream node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcMultiDatastreams node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeFeatureOfInterest node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcFeaturesOfInterest node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeHistLocation node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcHistLocations node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeLocation node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcLocations node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeSensor node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcSensors node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeThing node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcThings node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeObservation node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcObservations node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTeObservedProp node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcObservedProps node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpId node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpSelfLink node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpDescription node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpDefinition node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpEncodingType node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpFeature node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpLocation node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpMetadata node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpName node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpObservationType node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpMultiObservationDataTypes node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpPhenomenonTime node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpProperties node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpResult node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpResultTime node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpTime node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpUnitOfMeasurement node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpUnitOfMeasurements node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTcpRef node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTppValue node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTppSubProperty node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTppArrayIndex node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTLong node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTString node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpObservedArea node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpParameters node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpResultQuality node, ResourcePath data) { - return defltAction(node, data); - } - - @Override - public ResourcePath visit(ASTpValidTime node, ResourcePath data) { - return defltAction(node, data); - } - -} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/DumpVisitorHtml.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/DumpVisitorHtml.java deleted file mode 100644 index 147683fae..000000000 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/DumpVisitorHtml.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.parser.path; - -import java.io.PrintWriter; - -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; - -public class DumpVisitorHtml extends DumpVisitor { - - private int addOl = 0; - private final PrintWriter out; - - public DumpVisitorHtml(PrintWriter out) { - this.out = out; - } - - private String prependString() { - StringBuilder sb = new StringBuilder(); - while (addOl > 0) { - sb.append("
    "); - addOl--; - } - sb.append("
  • "); - return sb.toString(); - } - - private String appendString() { - StringBuilder sb = new StringBuilder(); - while (addOl < 0) { - sb.append("
"); - addOl++; - } - sb.append(""); - return sb.toString(); - } - - @Override - public ResourcePath defltAction(SimpleNode node, ResourcePath data) { - if (node.value == null) { - out.println(prependString() + node); - } else { - out.println(prependString() + node.toString() + " : (" + node.value.getClass().getSimpleName() + ") " + node.value); - } - ++addOl; - node.childrenAccept(this, data); - --addOl; - out.println(appendString()); - return data; - } - - @Override - public ResourcePath visit(ASTStart node, ResourcePath data) { - out.println("
    "); - ResourcePath o = defltAction(node, data); - out.println("
"); - return o; - } - - @Override - public ResourcePath visit(SimpleNode node, ResourcePath data) { - out.println(prependString() + node + ": acceptor not implemented in subclass?{}"); - ++addOl; - node.childrenAccept(this, data); - --addOl; - out.println(appendString()); - return data; - } - -} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/PathParser.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/PathParser.java index 577ecea47..0428d1415 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/PathParser.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/path/PathParser.java @@ -26,16 +26,16 @@ import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManagerlong; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.Charset; -import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PathParser implements ParserVisitor { - private static final Charset ENCODING = Charset.forName("UTF-8"); /** * The logger for this class. */ @@ -43,25 +43,48 @@ public class PathParser implements ParserVisitor { private final IdManager idmanager; + /** + * Parse the given path with an IdManagerlong and UTF-8 encoding. + * + * @param serviceRootUrl The root url to use when parsing. + * @param path The path to parse. + * @return The parsed ResourcePath. + */ public static ResourcePath parsePath(String serviceRootUrl, String path) { - return parsePath(IdManager.ID_MANAGER_LONG, serviceRootUrl, path, ENCODING); + return parsePath(new IdManagerlong(), serviceRootUrl, path, StringHelper.ENCODING); } + /** + * Parse the given path, assuming UTF-8 encoding. + * + * @param idmanager The IdManager to use + * @param serviceRootUrl The root url to use when parsing. + * @param path The path to parse. + * @return The parsed ResourcePath. + */ public static ResourcePath parsePath(IdManager idmanager, String serviceRootUrl, String path) { - return parsePath(idmanager, serviceRootUrl, path, ENCODING); + return parsePath(idmanager, serviceRootUrl, path, StringHelper.ENCODING); } + /** + * Parse the given path. + * + * @param idmanager The IdManager to use + * @param serviceRootUrl The root url to use when parsing. + * @param path The path to parse. + * @param encoding The character encoding to use when parsing. + * @return The parsed ResourcePath. + */ public static ResourcePath parsePath(IdManager idmanager, String serviceRootUrl, String path, Charset encoding) { ResourcePath resourcePath = new ResourcePath(); resourcePath.setServiceRootUrl(serviceRootUrl); resourcePath.setPathUrl(path); - resourcePath.setPathElements(new ArrayList<>()); if (path == null) { return resourcePath; } LOGGER.debug("Parsing: {}", path); InputStream is = new ByteArrayInputStream(path.getBytes(encoding)); - Parser t = new Parser(is, ENCODING.name()); + Parser t = new Parser(is, StringHelper.ENCODING.name()); try { ASTStart start = t.Start(); PathParser v = new PathParser(idmanager); @@ -80,7 +103,7 @@ public PathParser(IdManager idmanager) { public ResourcePath defltAction(SimpleNode node, ResourcePath data) { if (node.value == null) { - LOGGER.debug(node.toString()); + LOGGER.debug("{}", node); } else { LOGGER.debug("{} : ({}){}", node, node.value.getClass().getSimpleName(), node.value); } @@ -96,30 +119,28 @@ private void addAsEntitiy(ResourcePath rp, SimpleNode node, EntityType type) { rp.setIdentifiedElement(epa); } epa.setParent(rp.getLastElement()); - rp.getPathElements().add(epa); - rp.setMainElement(epa); + rp.addPathElement(epa, true, false); } private void addAsEntitiySet(ResourcePath rp, EntityType type) { EntitySetPathElement espa = new EntitySetPathElement(); espa.setEntityType(type); espa.setParent(rp.getLastElement()); - rp.getPathElements().add(espa); - rp.setMainElement(espa); + rp.addPathElement(espa, true, false); } private void addAsEntitiyProperty(ResourcePath rp, EntityProperty type) { PropertyPathElement ppe = new PropertyPathElement(); ppe.setProperty(type); ppe.setParent(rp.getLastElement()); - rp.getPathElements().add(ppe); + rp.addPathElement(ppe); } private void addAsCustomProperty(ResourcePath rp, SimpleNode node) { CustomPropertyPathElement cppa = new CustomPropertyPathElement(); cppa.setName(node.value.toString()); cppa.setParent(rp.getLastElement()); - rp.getPathElements().add(cppa); + rp.addPathElement(cppa); } private void addAsArrayIndex(ResourcePath rp, SimpleNode node) { @@ -133,7 +154,7 @@ private void addAsArrayIndex(ResourcePath rp, SimpleNode node) { int index = Integer.parseInt(numberString); cpai.setIndex(index); cpai.setParent(rp.getLastElement()); - rp.getPathElements().add(cpai); + rp.addPathElement(cpai); } catch (NumberFormatException e) { throw new IllegalArgumentException("Array indices must be integer values. Failed to parse: " + image); } @@ -159,217 +180,217 @@ public ResourcePath visit(ASTIdentifiedPath node, ResourcePath data) { @Override public ResourcePath visit(ASTeDatastream node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.Datastream); + addAsEntitiy(data, node, EntityType.DATASTREAM); return defltAction(node, data); } @Override public ResourcePath visit(ASTcDatastreams node, ResourcePath data) { - addAsEntitiySet(data, EntityType.Datastream); + addAsEntitiySet(data, EntityType.DATASTREAM); return defltAction(node, data); } @Override public ResourcePath visit(ASTeMultiDatastream node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.MultiDatastream); + addAsEntitiy(data, node, EntityType.MULTIDATASTREAM); return defltAction(node, data); } @Override public ResourcePath visit(ASTcMultiDatastreams node, ResourcePath data) { - addAsEntitiySet(data, EntityType.MultiDatastream); + addAsEntitiySet(data, EntityType.MULTIDATASTREAM); return defltAction(node, data); } @Override public ResourcePath visit(ASTeFeatureOfInterest node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.FeatureOfInterest); + addAsEntitiy(data, node, EntityType.FEATUREOFINTEREST); return defltAction(node, data); } @Override public ResourcePath visit(ASTcFeaturesOfInterest node, ResourcePath data) { - addAsEntitiySet(data, EntityType.FeatureOfInterest); + addAsEntitiySet(data, EntityType.FEATUREOFINTEREST); return defltAction(node, data); } @Override public ResourcePath visit(ASTeHistLocation node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.HistoricalLocation); + addAsEntitiy(data, node, EntityType.HISTORICALLOCATION); return defltAction(node, data); } @Override public ResourcePath visit(ASTcHistLocations node, ResourcePath data) { - addAsEntitiySet(data, EntityType.HistoricalLocation); + addAsEntitiySet(data, EntityType.HISTORICALLOCATION); return defltAction(node, data); } @Override public ResourcePath visit(ASTeLocation node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.Location); + addAsEntitiy(data, node, EntityType.LOCATION); return defltAction(node, data); } @Override public ResourcePath visit(ASTcLocations node, ResourcePath data) { - addAsEntitiySet(data, EntityType.Location); + addAsEntitiySet(data, EntityType.LOCATION); return defltAction(node, data); } @Override public ResourcePath visit(ASTeSensor node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.Sensor); + addAsEntitiy(data, node, EntityType.SENSOR); return defltAction(node, data); } @Override public ResourcePath visit(ASTcSensors node, ResourcePath data) { - addAsEntitiySet(data, EntityType.Sensor); + addAsEntitiySet(data, EntityType.SENSOR); return defltAction(node, data); } @Override public ResourcePath visit(ASTeThing node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.Thing); + addAsEntitiy(data, node, EntityType.THING); return defltAction(node, data); } @Override public ResourcePath visit(ASTcThings node, ResourcePath data) { - addAsEntitiySet(data, EntityType.Thing); + addAsEntitiySet(data, EntityType.THING); return defltAction(node, data); } @Override public ResourcePath visit(ASTeObservation node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.Observation); + addAsEntitiy(data, node, EntityType.OBSERVATION); return defltAction(node, data); } @Override public ResourcePath visit(ASTcObservations node, ResourcePath data) { - addAsEntitiySet(data, EntityType.Observation); + addAsEntitiySet(data, EntityType.OBSERVATION); return defltAction(node, data); } @Override public ResourcePath visit(ASTeObservedProp node, ResourcePath data) { - addAsEntitiy(data, node, EntityType.ObservedProperty); + addAsEntitiy(data, node, EntityType.OBSERVEDPROPERTY); return defltAction(node, data); } @Override public ResourcePath visit(ASTcObservedProps node, ResourcePath data) { - addAsEntitiySet(data, EntityType.ObservedProperty); + addAsEntitiySet(data, EntityType.OBSERVEDPROPERTY); return defltAction(node, data); } @Override public ResourcePath visit(ASTpId node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Id); + addAsEntitiyProperty(data, EntityProperty.ID); return defltAction(node, data); } @Override public ResourcePath visit(ASTpSelfLink node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.SelfLink); + addAsEntitiyProperty(data, EntityProperty.SELFLINK); return defltAction(node, data); } @Override public ResourcePath visit(ASTpDescription node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Description); + addAsEntitiyProperty(data, EntityProperty.DESCRIPTION); return defltAction(node, data); } @Override public ResourcePath visit(ASTpDefinition node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Definition); + addAsEntitiyProperty(data, EntityProperty.DEFINITION); return defltAction(node, data); } @Override public ResourcePath visit(ASTpEncodingType node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.EncodingType); + addAsEntitiyProperty(data, EntityProperty.ENCODINGTYPE); return defltAction(node, data); } @Override public ResourcePath visit(ASTpFeature node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Feature); + addAsEntitiyProperty(data, EntityProperty.FEATURE); return defltAction(node, data); } @Override public ResourcePath visit(ASTpLocation node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Location); + addAsEntitiyProperty(data, EntityProperty.LOCATION); return defltAction(node, data); } @Override public ResourcePath visit(ASTpMetadata node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Metadata); + addAsEntitiyProperty(data, EntityProperty.METADATA); return defltAction(node, data); } @Override public ResourcePath visit(ASTpName node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Name); + addAsEntitiyProperty(data, EntityProperty.NAME); return defltAction(node, data); } @Override public ResourcePath visit(ASTpObservationType node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.ObservationType); + addAsEntitiyProperty(data, EntityProperty.OBSERVATIONTYPE); return defltAction(node, data); } @Override public ResourcePath visit(ASTpMultiObservationDataTypes node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.MultiObservationDataTypes); + addAsEntitiyProperty(data, EntityProperty.MULTIOBSERVATIONDATATYPES); return defltAction(node, data); } @Override public ResourcePath visit(ASTpPhenomenonTime node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.PhenomenonTime); + addAsEntitiyProperty(data, EntityProperty.PHENOMENONTIME); return defltAction(node, data); } @Override public ResourcePath visit(ASTpProperties node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Properties); + addAsEntitiyProperty(data, EntityProperty.PROPERTIES); return defltAction(node, data); } @Override public ResourcePath visit(ASTpResult node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Result); + addAsEntitiyProperty(data, EntityProperty.RESULT); return defltAction(node, data); } @Override public ResourcePath visit(ASTpResultTime node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.ResultTime); + addAsEntitiyProperty(data, EntityProperty.RESULTTIME); return defltAction(node, data); } @Override public ResourcePath visit(ASTpTime node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Time); + addAsEntitiyProperty(data, EntityProperty.TIME); return defltAction(node, data); } @Override public ResourcePath visit(ASTpUnitOfMeasurement node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.UnitOfMeasurement); + addAsEntitiyProperty(data, EntityProperty.UNITOFMEASUREMENT); return defltAction(node, data); } @Override public ResourcePath visit(ASTpUnitOfMeasurements node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.UnitOfMeasurements); + addAsEntitiyProperty(data, EntityProperty.UNITOFMEASUREMENTS); return defltAction(node, data); } @@ -409,25 +430,25 @@ public ResourcePath visit(ASTString node, ResourcePath data) { @Override public ResourcePath visit(ASTpObservedArea node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.ObservedArea); + addAsEntitiyProperty(data, EntityProperty.OBSERVEDAREA); return defltAction(node, data); } @Override public ResourcePath visit(ASTpParameters node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.Parameters); + addAsEntitiyProperty(data, EntityProperty.PARAMETERS); return defltAction(node, data); } @Override public ResourcePath visit(ASTpResultQuality node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.ResultQuality); + addAsEntitiyProperty(data, EntityProperty.RESULTQUALITY); return defltAction(node, data); } @Override public ResourcePath visit(ASTpValidTime node, ResourcePath data) { - addAsEntitiyProperty(data, EntityProperty.ValidTime); + addAsEntitiyProperty(data, EntityProperty.VALIDTIME); return defltAction(node, data); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/DumpVisitor.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/DumpVisitor.java deleted file mode 100644 index 610be81e6..000000000 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/DumpVisitor.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.parser.query; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DumpVisitor implements ParserVisitor { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(DumpVisitor.class); - private int indent = 0; - - private String indentString() { - StringBuilder sb = new StringBuilder(); - boolean line = true; - for (int i = 0; i < indent; ++i) { - if (line) { - sb.append("| "); - } else { - sb.append(" "); - } - line = !line; - } - return sb.toString(); - } - - public Object defltAction(SimpleNode node, Object data) { - if (node.value == null) { - LOGGER.info(indentString() + node); - } else { - LOGGER.info(indentString() + node + " : (" + node.value.getClass().getName() + ") " + node.value); - } - ++indent; - node.childrenAccept(this, data); - --indent; - return data; - } - - @Override - public Object visit(SimpleNode node, Object data) { - LOGGER.info("{}{}: acceptor not implemented in subclass?", indentString(), node); - ++indent; - node.childrenAccept(this, data); - --indent; - return data; - } - - @Override - public Object visit(ASTStart node, Object data) { - LOGGER.info(indentString() + node); - ++indent; - node.childrenAccept(this, data); - --indent; - return data; - } - - @Override - public Object visit(ASTOptions node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTOption node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTOrderBys node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTOrderBy node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTIdentifiers node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTPlainPaths node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTPlainPath node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTPathElement node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTFilteredPaths node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTFilteredPath node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTIdentifiedPaths node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTIdentifiedPath node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTFilter node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTFormat node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTLogicalOr node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTLogicalAnd node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTNot node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTBooleanFunction node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTComparison node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTPlusMin node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTOperator node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTMulDiv node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTValueNode node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTBool node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTFunction node, Object data) { - return defltAction(node, data); - } - - @Override - public Object visit(ASTGeoStringLit node, Object data) { - return defltAction(node, data); - } - -} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/DumpVisitorHtml.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/DumpVisitorHtml.java deleted file mode 100644 index 185dd1a75..000000000 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/DumpVisitorHtml.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.parser.query; - -import java.io.PrintWriter; - -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; - -public class DumpVisitorHtml extends DumpVisitor { - - private int addOl = 0; - private final PrintWriter out; - - public DumpVisitorHtml(PrintWriter out) { - this.out = out; - } - - private String prependString() { - StringBuilder sb = new StringBuilder(); - while (addOl > 0) { - sb.append("
    "); - addOl--; - } - sb.append("
  • "); - return sb.toString(); - } - - private String appendString() { - StringBuilder sb = new StringBuilder(); - while (addOl < 0) { - sb.append("
"); - addOl++; - } - sb.append(""); - return sb.toString(); - } - - @Override - public Object defltAction(SimpleNode node, Object data) { - out.println(prependString() + node); - ++addOl; - node.childrenAccept(this, data); - --addOl; - out.println(appendString()); - return data; - } - - @Override - public Object visit(ASTStart node, Object data) { - out.println("
    "); - Object o = defltAction(node, data); - out.println("
"); - return o; - } - - @Override - public Object visit(SimpleNode node, Object data) { - out.println(prependString() + node + ": acceptor not implemented in subclass?{}"); - ++addOl; - node.childrenAccept(this, data); - --addOl; - out.println(appendString()); - return data; - } - -} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/ExpressionParser.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/ExpressionParser.java index 47143cc3d..b0f667eb3 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/ExpressionParser.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/ExpressionParser.java @@ -59,15 +59,15 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.Time; import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.TotalOffsetMinutes; import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.Year; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoDistance; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoIntersects; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoLength; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.And; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.Not; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.Or; import de.fraunhofer.iosb.ilt.sta.query.expression.function.math.Ceiling; import de.fraunhofer.iosb.ilt.sta.query.expression.function.math.Floor; import de.fraunhofer.iosb.ilt.sta.query.expression.function.math.Round; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoDistance; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoIntersects; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoLength; import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.STContains; import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.STCrosses; import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.STDisjoint; @@ -95,7 +95,9 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.function.temporal.Overlaps; import de.fraunhofer.iosb.ilt.sta.query.expression.function.temporal.Starts; import de.fraunhofer.iosb.ilt.sta.util.ParserHelper; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -104,6 +106,7 @@ import org.joda.time.LocalDate; import org.joda.time.LocalTime; import org.joda.time.Period; +import org.slf4j.LoggerFactory; /** * @@ -111,65 +114,112 @@ */ public class ExpressionParser extends AbstractParserVisitor { - private static final String OP_NOT = "not"; - private static final String OP_AND = "and"; - private static final String OP_OR = "or"; - private static final String OP_ADD = "+"; - private static final String OP_SUB = "-"; - private static final String OP_MUL = "mul"; - private static final String OP_DIV = "div"; - private static final String OP_MOD = "mod"; - private static final String OP_EQUAL = "eq"; - private static final String OP_NOT_EQUAL = "ne"; - private static final String OP_GREATER_THAN = "gt"; - private static final String OP_GREATER_EQUAL = "ge"; - private static final String OP_LESS_THAN = "lt"; - private static final String OP_LESS_EQUAL = "le"; - private static final String OP_SUBSTRING_OF = "substringof"; - private static final String OP_ENDS_WITH = "endswith"; - private static final String OP_STARTS_WITH = "startswith"; - private static final String OP_LENGTH = "length"; - private static final String OP_INDEX_OF = "indexof"; - private static final String OP_SUBSTRING = "substring"; - private static final String OP_TO_LOWER = "tolower"; - private static final String OP_TO_UPPER = "toupper"; - private static final String OP_TRIM = "trim"; - private static final String OP_CONCAT = "concat"; - private static final String OP_YEAR = "year"; - private static final String OP_MONTH = "month"; - private static final String OP_DAY = "day"; - private static final String OP_HOUR = "hour"; - private static final String OP_MINUTE = "minute"; - private static final String OP_SECOND = "second"; - private static final String OP_FRACTIONAL_SECONDS = "fractionalseconds"; - private static final String OP_DATE = "date"; - private static final String OP_TIME = "time"; - private static final String OP_TOTAL_OFFSET_MINUTES = "totaloffsetminutes"; - private static final String OP_NOW = "now"; - private static final String OP_MIN_DATETIME = "mindatetime"; - private static final String OP_MAX_DATETIME = "maxdatetime"; - private static final String OP_BEFORE = "before"; - private static final String OP_AFTER = "after"; - private static final String OP_MEETS = "meets"; - private static final String OP_DURING = "during"; - private static final String OP_OVERLAPS = "overlaps"; - private static final String OP_STARTS = "starts"; - private static final String OP_FINISHES = "finishes"; - private static final String OP_ROUND = "round"; - private static final String OP_FLOOR = "floor"; - private static final String OP_CEILING = "ceiling"; - private static final String OP_GEO_DISTANCE = "geo.distance"; - private static final String OP_GEO_LENGTH = "geo.length"; - private static final String OP_GEO_INTERSECTS = "geo.intersects"; - private static final String OP_ST_EQUALS = "st_equals"; - private static final String OP_ST_DISJOINT = "st_disjoint"; - private static final String OP_ST_TOUCHES = "st_touches"; - private static final String OP_ST_WITHIN = "st_within"; - private static final String OP_ST_OVERLAPS = "st_overlaps"; - private static final String OP_ST_CROSSES = "st_crosses"; - private static final String OP_ST_INTERSECTS = "st_intersects"; - private static final String OP_ST_CONTAINS = "st_contains"; - private static final String OP_ST_RELATE = "st_relate"; + /** + * The logger for this class. + */ + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ExpressionParser.class); + + public enum Operator { + // Logical + OP_NOT("not", Not.class), + OP_AND("and", And.class), + OP_OR("or", Or.class), + // Math + OP_ADD("+", Add.class), + OP_SUB("-", Subtract.class), + OP_MUL("mul", Multiply.class), + OP_DIV("div", Divide.class), + OP_MOD("mod", Modulo.class), + // Comparison + OP_EQUAL("eq", Equal.class), + OP_NOT_EQUAL("ne", NotEqual.class), + OP_GREATER_THAN("gt", GreaterThan.class), + OP_GREATER_EQUAL("ge", GreaterEqual.class), + OP_LESS_THAN("lt", LessThan.class), + OP_LESS_EQUAL("le", LessEqual.class), + // String + OP_SUBSTRING_OF("substringof", SubstringOf.class), + OP_ENDS_WITH("endswith", EndsWith.class), + OP_STARTS_WITH("startswith", StartsWith.class), + OP_LENGTH("length", Length.class), + OP_INDEX_OF("indexof", IndexOf.class), + OP_SUBSTRING("substring", Substring.class), + OP_TO_LOWER("tolower", ToLower.class), + OP_TO_UPPER("toupper", ToUpper.class), + OP_TRIM("trim", Trim.class), + OP_CONCAT("concat", Concat.class), + // DateTime + OP_YEAR("year", Year.class), + OP_MONTH("month", Month.class), + OP_DAY("day", Day.class), + OP_HOUR("hour", Hour.class), + OP_MINUTE("minute", Minute.class), + OP_SECOND("second", Second.class), + OP_FRACTIONAL_SECONDS("fractionalseconds", FractionalSeconds.class), + OP_DATE("date", Date.class), + OP_TIME("time", Time.class), + OP_TOTAL_OFFSET_MINUTES("totaloffsetminutes", TotalOffsetMinutes.class), + OP_NOW("now", Now.class), + OP_MIN_DATETIME("mindatetime", MinDateTime.class), + OP_MAX_DATETIME("maxdatetime", MaxDateTime.class), + // Allen's interval algebra + OP_BEFORE("before", Before.class), + OP_AFTER("after", After.class), + OP_MEETS("meets", Meets.class), + OP_DURING("during", During.class), + OP_OVERLAPS("overlaps", Overlaps.class), + OP_STARTS("starts", Starts.class), + OP_FINISHES("finishes", Finishes.class), + // Math + OP_ROUND("round", Round.class), + OP_FLOOR("floor", Floor.class), + OP_CEILING("ceiling", Ceiling.class), + // Geo + OP_GEO_DISTANCE("geo.distance", GeoDistance.class), + OP_GEO_LENGTH("geo.length", GeoLength.class), + OP_GEO_INTERSECTS("geo.intersects", GeoIntersects.class), + OP_ST_EQUALS("st_equals", STEquals.class), + OP_ST_DISJOINT("st_disjoint", STDisjoint.class), + OP_ST_TOUCHES("st_touches", STTouches.class), + OP_ST_WITHIN("st_within", STWithin.class), + OP_ST_OVERLAPS("st_overlaps", STOverlaps.class), + OP_ST_CROSSES("st_crosses", STCrosses.class), + OP_ST_INTERSECTS("st_intersects", STIntersects.class), + OP_ST_CONTAINS("st_contains", STContains.class), + OP_ST_RELATE("st_relate", STRelate.class); + + private static final Map byKey = new HashMap<>(); + + static { + for (Operator o : Operator.values()) { + byKey.put(o.urlKey, o); + } + } + public final String urlKey; + public final Class implementingClass; + + private Operator(String urlKey, Class implementingClass) { + this.urlKey = urlKey; + this.implementingClass = implementingClass; + } + + public Function instantiate() { + try { + return implementingClass.newInstance(); + } catch (InstantiationException | IllegalAccessException ex) { + LOGGER.error("Failed to instantiate function {}: {}", this, ex.getMessage()); + throw new IllegalStateException("problem executing '" + this + "'", ex); + } + } + + public static Operator fromKey(String key) { + Operator operator = byKey.get(key); + if (operator == null) { + throw new IllegalArgumentException("Unknown operator: '" + key + "'."); + } + return operator; + } + } public static Expression parseExpression(Node node) { return new ExpressionParser().visit(node, null); @@ -201,12 +251,11 @@ public Path visit(ASTPlainPath node, Object data) { @Override public Property visit(ASTPathElement node, Object data) { - // TODO only name or also ID allowed??? if (node.getIdentifier() != null && !node.getIdentifier().isEmpty()) { throw new IllegalArgumentException("no identified paths are allowed inside expressions"); } Property previous = null; - if (data != null && data instanceof Property) { + if (data instanceof Property) { previous = (Property) data; } return ParserHelper.parseProperty(node.getName(), previous); @@ -222,35 +271,22 @@ public Expression visit(ASTFilter node, Object data) { @Override public Or visit(ASTLogicalOr node, Object data) { - return (Or) visitLogicalFunction(OP_OR, node, data); + return (Or) visitLogicalFunction(Operator.OP_OR, node, data); } @Override public And visit(ASTLogicalAnd node, Object data) { - return (And) visitLogicalFunction(OP_AND, node, data); - } - - private Function getLogicalFunction(String operator) { - switch (operator) { - case OP_AND: { - return new And(); - } - case OP_OR: { - return new Or(); - } - default: - throw new IllegalArgumentException("unknown operator '" + operator + "'"); - } + return (And) visitLogicalFunction(Operator.OP_AND, node, data); } - private Function visitLogicalFunction(String operator, Node node, Object data) { + private Function visitLogicalFunction(Operator operator, Node node, Object data) { if (node.jjtGetNumChildren() < 2) { throw new IllegalArgumentException("'" + operator + "' must have at least two parameters"); } - Function function = getLogicalFunction(operator); + Function function = operator.instantiate(); Expression result = visitChildWithType(function, node.jjtGetChild(node.jjtGetNumChildren() - 1), data, 1); for (int i = node.jjtGetNumChildren() - 2; i >= 0; i--) { - function = getLogicalFunction(operator); + function = operator.instantiate(); Expression lhs = visitChildWithType(function, node.jjtGetChild(i), data, 0); function.setParameters(lhs, result); result = function; @@ -284,28 +320,6 @@ private Expression visit(Node node, Object data) { return (Expression) node.jjtAccept(this, data); } - private Function getArithmeticFunction(String operator) { - switch (operator) { - case OP_ADD: { - return new Add(); - } - case OP_SUB: { - return new Subtract(); - } - case OP_MUL: { - return new Multiply(); - } - case OP_DIV: { - return new Divide(); - } - case OP_MOD: { - return new Modulo(); - } - default: - throw new IllegalArgumentException("unknown operator '" + operator + "'"); - } - } - private Function visitArithmeticFunction(SimpleNode node, Object data) { int childCount = node.jjtGetNumChildren(); if (childCount < 3 || childCount % 2 == 0) { @@ -316,25 +330,26 @@ private Function visitArithmeticFunction(SimpleNode node, Object data) { Expression rhs; Expression lhs; Function result = null; - for (int i = 0; i < childCount; i++) { - assert (i < childCount - 1); - int operatorIndex = result == null ? i + 1 : i; + int idx = 0; + while (idx < childCount) { + int operatorIndex = result == null ? idx + 1 : idx; if (!(node.jjtGetChild(operatorIndex) instanceof ASTOperator)) { - throw new IllegalArgumentException("operator expected but '" + node.jjtGetChild(i).getClass().getName() + "' found"); + throw new IllegalArgumentException("operator expected but '" + node.jjtGetChild(idx).getClass().getName() + "' found"); } - String operator = ((ASTOperator) node.jjtGetChild(operatorIndex)).getName().trim().toLowerCase(); - Function function = getArithmeticFunction(operator); + String operatorKey = ((ASTOperator) node.jjtGetChild(operatorIndex)).getName().trim().toLowerCase(); + Function function = getFunction(operatorKey); if (result == null) { - lhs = visitChildWithType(function, node.jjtGetChild(i), data, 1); - i++; + lhs = visitChildWithType(function, node.jjtGetChild(idx), data, 1); + idx++; } else { lhs = result; } - i++; - rhs = visitChildWithType(function, node.jjtGetChild(i), data, 0); + idx++; + rhs = visitChildWithType(function, node.jjtGetChild(idx), data, 0); function.setParameters(lhs, rhs); result = function; + idx++; } return result; } @@ -350,168 +365,7 @@ public Function visit(ASTMulDiv node, Object data) { } private Function getFunction(String operator) { - switch (operator) { - /* comparison functions */ - case OP_NOT: { - return new Not(); - } - case OP_EQUAL: { - return new Equal(); - } - case OP_NOT_EQUAL: { - return new NotEqual(); - } - case OP_GREATER_THAN: { - return new GreaterThan(); - } - case OP_GREATER_EQUAL: { - return new GreaterEqual(); - } - case OP_LESS_THAN: { - return new LessThan(); - } - case OP_LESS_EQUAL: { - return new LessEqual(); - } - /* string functions */ - case OP_SUBSTRING_OF: { - return new SubstringOf(); - } - case OP_ENDS_WITH: { - return new EndsWith(); - } - case OP_STARTS_WITH: { - return new StartsWith(); - } - case OP_LENGTH: { - return new Length(); - } - case OP_INDEX_OF: { - return new IndexOf(); - } - case OP_SUBSTRING: { - return new Substring(); - } - case OP_TO_LOWER: { - return new ToLower(); - } - case OP_TO_UPPER: { - return new ToUpper(); - } - case OP_TRIM: { - return new Trim(); - } - case OP_CONCAT: { - return new Concat(); - } - case OP_YEAR: { - return new Year(); - } - case OP_MONTH: { - return new Month(); - } - case OP_DAY: { - return new Day(); - } - case OP_HOUR: { - return new Hour(); - } - case OP_MINUTE: { - return new Minute(); - } - case OP_SECOND: { - return new Second(); - } - case OP_FRACTIONAL_SECONDS: { - return new FractionalSeconds(); - } - case OP_DATE: { - return new Date(); - } - case OP_TIME: { - return new Time(); - } - case OP_TOTAL_OFFSET_MINUTES: { - return new TotalOffsetMinutes(); - } - case OP_NOW: { - return new Now(); - } - case OP_MIN_DATETIME: { - return new MinDateTime(); - } - case OP_MAX_DATETIME: { - return new MaxDateTime(); - } - case OP_BEFORE: { - return new Before(); - } - case OP_AFTER: { - return new After(); - } - case OP_MEETS: { - return new Meets(); - } - case OP_DURING: { - return new During(); - } - case OP_OVERLAPS: { - return new Overlaps(); - } - case OP_STARTS: { - return new Starts(); - } - case OP_FINISHES: { - return new Finishes(); - } - case OP_ROUND: { - return new Round(); - } - case OP_FLOOR: { - return new Floor(); - } - case OP_CEILING: { - return new Ceiling(); - } - case OP_GEO_DISTANCE: { - return new GeoDistance(); - } - case OP_GEO_LENGTH: { - return new GeoLength(); - } - case OP_GEO_INTERSECTS: { - return new GeoIntersects(); - } - case OP_ST_EQUALS: { - return new STEquals(); - } - case OP_ST_DISJOINT: { - return new STDisjoint(); - } - case OP_ST_TOUCHES: { - return new STTouches(); - } - case OP_ST_WITHIN: { - return new STWithin(); - } - case OP_ST_OVERLAPS: { - return new STOverlaps(); - } - case OP_ST_CROSSES: { - return new STCrosses(); - } - case OP_ST_INTERSECTS: { - return new STIntersects(); - } - case OP_ST_CONTAINS: { - return new STContains(); - } - case OP_ST_RELATE: { - return new STRelate(); - } - default: - throw new IllegalArgumentException("unknown function '" + operator + "'"); - } + return Operator.fromKey(operator).instantiate(); } @Override @@ -525,12 +379,8 @@ public Function visit(ASTFunction node, Object data) { private Expression[] visitChildsWithType(Function function, Node node, Object data) { List allowedBindings = function.getAllowedTypeBindings(); if (data != null) { - try { - List> allowedReturnTypes = (List>) data; - allowedBindings = allowedBindings.stream().filter(x -> allowedReturnTypes.contains(x.getReturnType())).collect(Collectors.toList()); - } catch (Exception e) { - - } + List> allowedReturnTypes = (List>) data; + allowedBindings = allowedBindings.stream().filter(x -> allowedReturnTypes.contains(x.getReturnType())).collect(Collectors.toList()); } Expression[] parameters = new Expression[node.jjtGetNumChildren()]; for (int i = 0; i < parameters.length; i++) { @@ -542,12 +392,8 @@ private Expression[] visitChildsWithType(Function function, Node node, Object da private Expression visitChildWithType(Function function, Node child, Object data, int parameterIndex) { List allowedBindings = function.getAllowedTypeBindings(); if (data != null) { - try { - List> allowedReturnTypes = (List>) data; - allowedBindings = allowedBindings.stream().filter(x -> allowedReturnTypes.contains(x.getReturnType())).collect(Collectors.toList()); - } catch (Exception e) { - - } + List> allowedReturnTypes = (List>) data; + allowedBindings = allowedBindings.stream().filter(x -> allowedReturnTypes.contains(x.getReturnType())).collect(Collectors.toList()); } return visit(child, allowedBindings.stream().map(x -> x.getParameters().get(parameterIndex)).collect(Collectors.toList())); } @@ -562,7 +408,7 @@ public Constant visit(ASTValueNode node, Object data) { } else if (value instanceof Integer) { return new IntegerConstant((Integer) value); } else if (value instanceof Long) { - return new IntegerConstant(((Long) value).intValue()); + return new IntegerConstant(((Number) value).intValue()); } else if (value instanceof DateTime) { return new DateTimeConstant((DateTime) value); } else if (value instanceof LocalDate) { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/QueryParser.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/QueryParser.java index 4589b54fe..e839fa50d 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/QueryParser.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/QueryParser.java @@ -24,6 +24,7 @@ import de.fraunhofer.iosb.ilt.sta.query.Query; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; import de.fraunhofer.iosb.ilt.sta.util.ParserHelper; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.Charset; @@ -38,7 +39,7 @@ public class QueryParser extends AbstractParserVisitor { * The logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(QueryParser.class); - private static final Charset ENCODING = Charset.forName("UTF-8"); + private final CoreSettings settings; public QueryParser(CoreSettings settings) { @@ -50,22 +51,22 @@ public static Query parseQuery(String query) { } public static Query parseQuery(String query, CoreSettings settings) { - return parseQuery(query, ENCODING, settings); + return parseQuery(query, StringHelper.ENCODING, settings); } public static Query parseQuery(String query, Charset encoding, CoreSettings settings) { if (query == null || query.isEmpty()) { - Query result = new Query(settings); - return result; + return new Query(settings); } - LOGGER.debug("Parsing: {}", query); + InputStream is = new ByteArrayInputStream(query.getBytes(encoding)); - Parser t = new Parser(is, ENCODING.name()); + Parser t = new Parser(is, StringHelper.ENCODING.name()); try { ASTStart n = t.Start(); QueryParser v = new QueryParser(settings); return v.visit(n, null); } catch (ParseException | TokenMgrError | IllegalArgumentException ex) { + LOGGER.error("Exception parsing: {}", query); LOGGER.error("Failed to parse because (Set loglevel to trace for stack): {}", ex.getMessage()); LOGGER.trace("Exception: ", ex); throw new IllegalArgumentException("Query is not valid: " + ex.getMessage(), ex); @@ -101,51 +102,50 @@ public Object visit(ASTOption node, Object data) { Query query = (Query) data; String operator = node.getType().toLowerCase().trim(); switch (operator) { - case OP_TOP: { + case OP_TOP: int top = Math.toIntExact((long) ((ASTValueNode) node.jjtGetChild(0)).jjtGetValue()); query.setTop(top); break; - } - case OP_SKIP: { + + case OP_SKIP: query.setSkip(Math.toIntExact((long) ((ASTValueNode) node.jjtGetChild(0)).jjtGetValue())); break; - } - case OP_COUNT: { + + case OP_COUNT: query.setCount(((ASTBool) node.jjtGetChild(0)).getValue()); break; - } - case OP_SELECT: { + + case OP_SELECT: if (node.jjtGetNumChildren() != 1 || !(node.jjtGetChild(0) instanceof ASTIdentifiers)) { throw new IllegalArgumentException("ASTOption(select) must have exactly one child node of type ASTIdentifiers"); } query.setSelect(visit((ASTIdentifiers) node.jjtGetChild(0), data)); break; - } - case OP_EXPAND: { + + case OP_EXPAND: if (node.jjtGetNumChildren() != 1 || !(node.jjtGetChild(0) instanceof ASTFilteredPaths)) { throw new IllegalArgumentException("ASTOption(expand) must have exactly one child node of type ASTFilteredPaths"); } query.setExpand(visit(((ASTFilteredPaths) node.jjtGetChild(0)), data)); break; - } - case OP_FILTER: { + + case OP_FILTER: if (node.jjtGetNumChildren() != 1) { throw new IllegalArgumentException("ASTOption(filter) must have exactly one child node"); } query.setFilter(ExpressionParser.parseExpression(node.jjtGetChild(0))); break; - } - case OP_FORMAT: { + + case OP_FORMAT: query.setFormat(((ASTFormat) node.jjtGetChild(0)).getValue()); break; - } - case OP_ORDER_BY: { + + case OP_ORDER_BY: if (node.jjtGetNumChildren() != 1 || !(node.jjtGetChild(0) instanceof ASTOrderBys)) { throw new IllegalArgumentException("ASTOption(orderby) must have exactly one child node of type ASTOrderBys"); } query.setOrderBy(visit((ASTOrderBys) node.jjtGetChild(0), data)); break; - } default: // ignore or throw exception? @@ -206,7 +206,7 @@ public Property visit(ASTPathElement node, Object data) { throw new IllegalArgumentException("no identified paths are allowed inside select"); } Property previous = null; - if (data != null && data instanceof Property) { + if (data instanceof Property) { previous = (Property) data; } return ParserHelper.parseProperty(node.getName(), previous); @@ -228,6 +228,6 @@ public OrderBy visit(ASTOrderBy node, Object data) { } return new OrderBy( ExpressionParser.parseExpression(node.jjtGetChild(0)), - node.isAscending() ? OrderBy.OrderType.Ascending : OrderBy.OrderType.Descending); + node.isAscending() ? OrderBy.OrderType.ASCENDING : OrderBy.OrderType.DESCENDING); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomProperty.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomProperty.java index 56a47d318..627820e04 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomProperty.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomProperty.java @@ -24,6 +24,7 @@ */ public class CustomProperty implements Property { + private static final String UNSUPPORTED = "Not supported on custom properties."; /** * The name of this property as used in URLs. */ @@ -58,18 +59,28 @@ public String getName() { return name; } + @Override + public String getJsonName() { + return name; + } + public Integer getIndex() { return index; } @Override public String getGetterName() { - throw new UnsupportedOperationException("Not supported on custom properties."); + throw new UnsupportedOperationException(UNSUPPORTED); } @Override public String getSetterName() { - throw new UnsupportedOperationException("Not supported on custom properties."); + throw new UnsupportedOperationException(UNSUPPORTED); + } + + @Override + public String getIsSetName() { + throw new UnsupportedOperationException(UNSUPPORTED); } @Override @@ -83,10 +94,7 @@ public String toString() { @Override public int hashCode() { - int hash = 5; - hash = 17 * hash + Objects.hashCode(this.name); - hash = 17 * hash + Objects.hashCode(this.index); - return hash; + return Objects.hash(name, index); } @Override @@ -101,10 +109,8 @@ public boolean equals(Object obj) { return false; } final CustomProperty other = (CustomProperty) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - return Objects.equals(this.index, other.index); + return Objects.equals(this.name, other.name) + && Objects.equals(this.index, other.index); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyArrayIndex.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyArrayIndex.java index e83d12c4b..10d3eabc2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyArrayIndex.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyArrayIndex.java @@ -65,10 +65,7 @@ public String toString() { @Override public int hashCode() { - int hash = 3; - hash = 31 * hash + Objects.hashCode(this.index); - hash = 31 * hash + Objects.hashCode(this.parent); - return hash; + return Objects.hash(index, parent); } @Override @@ -80,10 +77,8 @@ public boolean equals(Object obj) { return false; } final CustomPropertyArrayIndex other = (CustomPropertyArrayIndex) obj; - if (!Objects.equals(this.index, other.index)) { - return false; - } - return Objects.equals(this.parent, other.parent); + return Objects.equals(this.index, other.index) + && Objects.equals(this.parent, other.parent); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyPathElement.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyPathElement.java index cd1059800..ff56b75ef 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyPathElement.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/CustomPropertyPathElement.java @@ -65,10 +65,7 @@ public String toString() { @Override public int hashCode() { - int hash = 3; - hash = 31 * hash + Objects.hashCode(this.name); - hash = 31 * hash + Objects.hashCode(this.parent); - return hash; + return Objects.hash(name, parent); } @Override @@ -80,13 +77,8 @@ public boolean equals(Object obj) { return false; } final CustomPropertyPathElement other = (CustomPropertyPathElement) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.parent, other.parent)) { - return false; - } - return true; + return Objects.equals(this.name, other.name) + && Objects.equals(this.parent, other.parent); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityPathElement.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityPathElement.java index 21addf92d..274526e77 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityPathElement.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityPathElement.java @@ -17,7 +17,7 @@ */ package de.fraunhofer.iosb.ilt.sta.path; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import java.util.Objects; /** @@ -71,16 +71,12 @@ public void visit(ResourcePathVisitor visitor) { @Override public String toString() { - return entityType.name; + return entityType.entityName; } @Override public int hashCode() { - int hash = 7; - hash = 11 * hash + Objects.hashCode(this.id); - hash = 11 * hash + Objects.hashCode(this.entityType); - hash = 11 * hash + Objects.hashCode(this.parent); - return hash; + return Objects.hash(id, entityType, parent); } @Override @@ -92,16 +88,9 @@ public boolean equals(Object obj) { return false; } final EntityPathElement other = (EntityPathElement) obj; - if (!Objects.equals(this.id, other.id)) { - return false; - } - if (this.entityType != other.entityType) { - return false; - } - if (!Objects.equals(this.parent, other.parent)) { - return false; - } - return true; + return Objects.equals(this.id, other.id) + && this.entityType == other.entityType + && Objects.equals(this.parent, other.parent); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityProperty.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityProperty.java index fc3986e1e..07b92f879 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityProperty.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityProperty.java @@ -17,6 +17,7 @@ */ package de.fraunhofer.iosb.ilt.sta.path; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -27,58 +28,66 @@ */ public enum EntityProperty implements Property { - Description, - Definition, - EncodingType, - Feature(true), - Id("id", "@iot.id"), - Location(true), - Metadata, - MultiObservationDataTypes, - Name, - ObservationType, - ObservedArea, - PhenomenonTime, - Parameters(true), - Properties(true), - Result(true), - ResultTime, - ResultQuality, - SelfLink("@iot.selfLink", "@iot.selfLink"), - Time, - UnitOfMeasurement(true), - UnitOfMeasurements(true), - ValidTime; + DESCRIPTION("Description"), + DEFINITION("Definition"), + ENCODINGTYPE("EncodingType"), + FEATURE("Feature", true), + ID("Id", "id", "@iot.id"), + LOCATION("Location", true), + METADATA("Metadata"), + MULTIOBSERVATIONDATATYPES("MultiObservationDataTypes"), + NAME("Name"), + OBSERVATIONTYPE("ObservationType"), + OBSERVEDAREA("ObservedArea"), + PHENOMENONTIME("PhenomenonTime"), + PARAMETERS("Parameters", true), + PROPERTIES("Properties", true), + RESULT("Result", true), + RESULTTIME("ResultTime"), + RESULTQUALITY("ResultQuality"), + SELFLINK("SelfLink", "@iot.selfLink", "@iot.selfLink"), + TIME("Time"), + UNITOFMEASUREMENT("UnitOfMeasurement", true), + UNITOFMEASUREMENTS("UnitOfMeasurements", true), + VALIDTIME("ValidTime"); /** - * The name of this property as used in URLs. + * The entitiyName of this property as used in URLs. */ - public final String name; + public final String entitiyName; + public final String jsonName; public final String getterName; public final String setterName; + public final String isSetName; public final boolean hasCustomProperties; private final Collection aliases; - private EntityProperty() { - this(false); + private EntityProperty(String codeName) { + this(codeName, false); } - private EntityProperty(boolean hasCustomProperties) { + private EntityProperty(String codeName, boolean hasCustomProperties) { this.aliases = new ArrayList<>(); - this.aliases.add(name()); - this.name = name().substring(0, 1).toLowerCase() + name().substring(1); - this.getterName = "get" + name(); - this.setterName = "set" + name(); + this.aliases.add(codeName); + this.entitiyName = StringHelper.deCapitalize(codeName); + this.jsonName = entitiyName; + this.getterName = "get" + codeName; + this.setterName = "set" + codeName; + this.isSetName = "isSet" + codeName; this.hasCustomProperties = hasCustomProperties; } - private EntityProperty(String name, String... aliases) { + private EntityProperty(String codeName, String pathName, String jsonName, String... aliases) { this.aliases = new ArrayList<>(); + this.entitiyName = pathName; + this.jsonName = jsonName; this.aliases.add(name()); - this.name = name; + this.aliases.add(jsonName); this.aliases.addAll(Arrays.asList(aliases)); - this.getterName = "get" + name(); - this.setterName = "set" + name(); + String capitalized = StringHelper.capitalize(codeName); + this.getterName = "get" + capitalized; + this.setterName = "set" + capitalized; + this.isSetName = "isSet" + capitalized; this.hasCustomProperties = false; } @@ -95,7 +104,12 @@ public static EntityProperty fromString(String propertyName) { @Override public String getName() { - return name; + return entitiyName; + } + + @Override + public String getJsonName() { + return jsonName; } @Override @@ -108,4 +122,9 @@ public String getSetterName() { return setterName; } + @Override + public String getIsSetName() { + return isSetName; + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntitySetPathElement.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntitySetPathElement.java index 72fc89064..1f05cca7a 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntitySetPathElement.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntitySetPathElement.java @@ -65,10 +65,7 @@ public String toString() { @Override public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.entityType); - hash = 53 * hash + Objects.hashCode(this.parent); - return hash; + return Objects.hash(entityType, parent); } @Override @@ -80,13 +77,8 @@ public boolean equals(Object obj) { return false; } final EntitySetPathElement other = (EntitySetPathElement) obj; - if (this.entityType != other.entityType) { - return false; - } - if (!Objects.equals(this.parent, other.parent)) { - return false; - } - return true; + return this.entityType == other.entityType + && Objects.equals(this.parent, other.parent); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityType.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityType.java index ea77eca5a..528efeba9 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityType.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/EntityType.java @@ -27,7 +27,9 @@ import de.fraunhofer.iosb.ilt.sta.model.Sensor; import de.fraunhofer.iosb.ilt.sta.model.Thing; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -37,15 +39,15 @@ */ public enum EntityType { - Datastream("Datastreams", Datastream.class), - MultiDatastream("MultiDatastreams", MultiDatastream.class), - FeatureOfInterest("FeaturesOfInterest", FeatureOfInterest.class), - HistoricalLocation("HistoricalLocations", HistoricalLocation.class), - Location("Locations", Location.class), - Observation("Observations", Observation.class), - ObservedProperty("ObservedProperties", ObservedProperty.class), - Sensor("Sensors", Sensor.class), - Thing("Things", Thing.class); + DATASTREAM("Datastream", "Datastreams", Datastream.class), + MULTIDATASTREAM("MultiDatastream", "MultiDatastreams", MultiDatastream.class), + FEATUREOFINTEREST("FeatureOfInterest", "FeaturesOfInterest", FeatureOfInterest.class), + HISTORICALLOCATION("HistoricalLocation", "HistoricalLocations", HistoricalLocation.class), + LOCATION("Location", "Locations", Location.class), + OBSERVATION("Observation", "Observations", Observation.class), + OBSERVEDPROPERTY("ObservedProperty", "ObservedProperties", ObservedProperty.class), + SENSOR("Sensor", "Sensors", Sensor.class), + THING("Thing", "Things", Thing.class); public static class PropertyEntry { @@ -62,141 +64,169 @@ public PropertyEntry(Property property, boolean required) { } /** - * The name of this entity type as used in URLs. + * The entitiyName of this entity type as used in URLs. */ - public final String name; + public final String entityName; /** - * The name of collections of this entity type as used in URLs. + * The entitiyName of collections of this entity type as used in URLs. */ public final String plural; /** - * The Set of Properties that Entities of this type have. + * The writable version of the properties map, for internal use only. */ - private final Map propertyMap = new HashMap<>(); + private final Map propertyMapRw = new HashMap<>(); + /** + * The Set of PROPERTIES that Entities of this type have, mapped to the flag + * indicating if they are required. + */ + private final Map propertyMap = Collections.unmodifiableMap(propertyMapRw); + /** + * The set of Navigation properties pointing to single entities. + */ + private final Set navigationEntities = new HashSet<>(); + /** + * The set of Navigation properties pointing to entity sets. + */ + private final Set navigationSets = new HashSet<>(); + private final Class implementingClass; private static void init() { - Map propertySet; - propertySet = Datastream.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Name, true); - propertySet.put(EntityProperty.Description, true); - propertySet.put(EntityProperty.ObservationType, true); - propertySet.put(EntityProperty.UnitOfMeasurement, true); - propertySet.put(EntityProperty.ObservedArea, false); - propertySet.put(EntityProperty.PhenomenonTime, false); - propertySet.put(EntityProperty.Properties, false); - propertySet.put(EntityProperty.ResultTime, false); - propertySet.put(NavigationProperty.ObservedProperty, true); - propertySet.put(NavigationProperty.Sensor, true); - propertySet.put(NavigationProperty.Thing, true); - propertySet.put(NavigationProperty.Observations, false); - - propertySet = MultiDatastream.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Name, true); - propertySet.put(EntityProperty.Description, true); - propertySet.put(EntityProperty.ObservationType, true); - propertySet.put(EntityProperty.MultiObservationDataTypes, true); - propertySet.put(EntityProperty.UnitOfMeasurements, true); - propertySet.put(EntityProperty.ObservedArea, false); - propertySet.put(EntityProperty.PhenomenonTime, false); - propertySet.put(EntityProperty.Properties, false); - propertySet.put(EntityProperty.ResultTime, false); - propertySet.put(NavigationProperty.ObservedProperties, true); - propertySet.put(NavigationProperty.Sensor, true); - propertySet.put(NavigationProperty.Thing, true); - propertySet.put(NavigationProperty.Observations, false); - - propertySet = FeatureOfInterest.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Name, true); - propertySet.put(EntityProperty.Description, true); - propertySet.put(EntityProperty.EncodingType, true); - propertySet.put(EntityProperty.Feature, true); - propertySet.put(EntityProperty.Properties, false); - propertySet.put(NavigationProperty.Observations, false); - - propertySet = HistoricalLocation.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Time, true); - propertySet.put(NavigationProperty.Thing, true); - propertySet.put(NavigationProperty.Locations, false); - - propertySet = Location.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Name, true); - propertySet.put(EntityProperty.Description, true); - propertySet.put(EntityProperty.EncodingType, true); - propertySet.put(EntityProperty.Location, true); - propertySet.put(EntityProperty.Properties, false); - propertySet.put(NavigationProperty.HistoricalLocations, false); - propertySet.put(NavigationProperty.Things, false); - - propertySet = Observation.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.PhenomenonTime, false); - propertySet.put(EntityProperty.ResultTime, false); - propertySet.put(EntityProperty.Result, true); - propertySet.put(EntityProperty.ResultQuality, false); - propertySet.put(EntityProperty.ValidTime, false); - propertySet.put(EntityProperty.Parameters, false); + Map propertyMap; + propertyMap = DATASTREAM.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.NAME, true); + propertyMap.put(EntityProperty.DESCRIPTION, true); + propertyMap.put(EntityProperty.OBSERVATIONTYPE, true); + propertyMap.put(EntityProperty.UNITOFMEASUREMENT, true); + propertyMap.put(EntityProperty.OBSERVEDAREA, false); + propertyMap.put(EntityProperty.PHENOMENONTIME, false); + propertyMap.put(EntityProperty.PROPERTIES, false); + propertyMap.put(EntityProperty.RESULTTIME, false); + propertyMap.put(NavigationProperty.OBSERVEDPROPERTY, true); + propertyMap.put(NavigationProperty.SENSOR, true); + propertyMap.put(NavigationProperty.THING, true); + propertyMap.put(NavigationProperty.OBSERVATIONS, false); + + propertyMap = MULTIDATASTREAM.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.NAME, true); + propertyMap.put(EntityProperty.DESCRIPTION, true); + // OBSERVATIONTYPE is required, but must always be the same, thus we set it ourselves. + propertyMap.put(EntityProperty.OBSERVATIONTYPE, false); + propertyMap.put(EntityProperty.MULTIOBSERVATIONDATATYPES, true); + propertyMap.put(EntityProperty.UNITOFMEASUREMENTS, true); + propertyMap.put(EntityProperty.OBSERVEDAREA, false); + propertyMap.put(EntityProperty.PHENOMENONTIME, false); + propertyMap.put(EntityProperty.PROPERTIES, false); + propertyMap.put(EntityProperty.RESULTTIME, false); + propertyMap.put(NavigationProperty.OBSERVEDPROPERTIES, true); + propertyMap.put(NavigationProperty.SENSOR, true); + propertyMap.put(NavigationProperty.THING, true); + propertyMap.put(NavigationProperty.OBSERVATIONS, false); + + propertyMap = FEATUREOFINTEREST.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.NAME, true); + propertyMap.put(EntityProperty.DESCRIPTION, true); + propertyMap.put(EntityProperty.ENCODINGTYPE, true); + propertyMap.put(EntityProperty.FEATURE, true); + propertyMap.put(EntityProperty.PROPERTIES, false); + propertyMap.put(NavigationProperty.OBSERVATIONS, false); + + propertyMap = HISTORICALLOCATION.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.TIME, true); + propertyMap.put(NavigationProperty.THING, true); + propertyMap.put(NavigationProperty.LOCATIONS, false); + + propertyMap = LOCATION.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.NAME, true); + propertyMap.put(EntityProperty.DESCRIPTION, true); + propertyMap.put(EntityProperty.ENCODINGTYPE, true); + propertyMap.put(EntityProperty.LOCATION, true); + propertyMap.put(EntityProperty.PROPERTIES, false); + propertyMap.put(NavigationProperty.HISTORICALLOCATIONS, false); + propertyMap.put(NavigationProperty.THINGS, false); + + propertyMap = OBSERVATION.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.PHENOMENONTIME, false); + propertyMap.put(EntityProperty.RESULTTIME, false); + propertyMap.put(EntityProperty.RESULT, true); + propertyMap.put(EntityProperty.RESULTQUALITY, false); + propertyMap.put(EntityProperty.VALIDTIME, false); + propertyMap.put(EntityProperty.PARAMETERS, false); // One of the following two is mandatory. - propertySet.put(NavigationProperty.Datastream, false); - propertySet.put(NavigationProperty.MultiDatastream, false); - // FeatureOfInterest must be generated on the fly if not present. - propertySet.put(NavigationProperty.FeatureOfInterest, false); - - propertySet = ObservedProperty.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Name, true); - propertySet.put(EntityProperty.Definition, true); - propertySet.put(EntityProperty.Description, true); - propertySet.put(EntityProperty.Properties, false); - propertySet.put(NavigationProperty.Datastreams, false); - propertySet.put(NavigationProperty.MultiDatastreams, false); - - propertySet = Sensor.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Name, true); - propertySet.put(EntityProperty.Description, true); - propertySet.put(EntityProperty.EncodingType, true); - propertySet.put(EntityProperty.Metadata, true); - propertySet.put(EntityProperty.Properties, false); - propertySet.put(NavigationProperty.Datastreams, false); - propertySet.put(NavigationProperty.MultiDatastreams, false); - - propertySet = Thing.propertyMap; - propertySet.put(EntityProperty.Id, false); - propertySet.put(EntityProperty.SelfLink, false); - propertySet.put(EntityProperty.Name, true); - propertySet.put(EntityProperty.Description, true); - propertySet.put(EntityProperty.Properties, false); - propertySet.put(NavigationProperty.Locations, false); - propertySet.put(NavigationProperty.HistoricalLocations, false); - propertySet.put(NavigationProperty.Datastreams, false); - propertySet.put(NavigationProperty.MultiDatastreams, false); + propertyMap.put(NavigationProperty.DATASTREAM, false); + propertyMap.put(NavigationProperty.MULTIDATASTREAM, false); + // FEATUREOFINTEREST must be generated on the fly if not present. + propertyMap.put(NavigationProperty.FEATUREOFINTEREST, false); + + propertyMap = OBSERVEDPROPERTY.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.NAME, true); + propertyMap.put(EntityProperty.DEFINITION, true); + propertyMap.put(EntityProperty.DESCRIPTION, true); + propertyMap.put(EntityProperty.PROPERTIES, false); + propertyMap.put(NavigationProperty.DATASTREAMS, false); + propertyMap.put(NavigationProperty.MULTIDATASTREAMS, false); + + propertyMap = SENSOR.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.NAME, true); + propertyMap.put(EntityProperty.DESCRIPTION, true); + propertyMap.put(EntityProperty.ENCODINGTYPE, true); + propertyMap.put(EntityProperty.METADATA, true); + propertyMap.put(EntityProperty.PROPERTIES, false); + propertyMap.put(NavigationProperty.DATASTREAMS, false); + propertyMap.put(NavigationProperty.MULTIDATASTREAMS, false); + + propertyMap = THING.propertyMapRw; + propertyMap.put(EntityProperty.ID, false); + propertyMap.put(EntityProperty.SELFLINK, false); + propertyMap.put(EntityProperty.NAME, true); + propertyMap.put(EntityProperty.DESCRIPTION, true); + propertyMap.put(EntityProperty.PROPERTIES, false); + propertyMap.put(NavigationProperty.LOCATIONS, false); + propertyMap.put(NavigationProperty.HISTORICALLOCATIONS, false); + propertyMap.put(NavigationProperty.DATASTREAMS, false); + propertyMap.put(NavigationProperty.MULTIDATASTREAMS, false); + + for (EntityType type : EntityType.values()) { + for (Property property : type.getPropertySet()) { + if (property instanceof NavigationProperty) { + NavigationProperty navigationProperty = (NavigationProperty) property; + if (navigationProperty.isSet) { + type.getNavigationSets().add(navigationProperty); + } else { + type.getNavigationEntities().add(navigationProperty); + } + } + } + } } - private EntityType(String plural, Class implementingClass) { - this.name = name(); + private EntityType(String singular, String plural, Class implementingClass) { + this.entityName = singular; this.plural = plural; this.implementingClass = implementingClass; } /** - * The Map of Properties that Entities of this type have, with their + * The Map of PROPERTIES that Entities of this type have, with their * required status. * - * @return The Set of Properties that Entities of this type have. + * @return The Set of PROPERTIES that Entities of this type have. */ public Map getPropertyMap() { if (propertyMap.isEmpty()) { @@ -206,9 +236,9 @@ public Map getPropertyMap() { } /** - * The Set of Properties that Entities of this type have. + * The Set of PROPERTIES that Entities of this type have. * - * @return The Set of Properties that Entities of this type have. + * @return The Set of PROPERTIES that Entities of this type have. */ public Set getPropertySet() { if (propertyMap.isEmpty()) { @@ -217,6 +247,20 @@ public Set getPropertySet() { return propertyMap.keySet(); } + public Set getNavigationEntities() { + if (propertyMap.isEmpty()) { + init(); + } + return navigationEntities; + } + + public Set getNavigationSets() { + if (propertyMap.isEmpty()) { + init(); + } + return navigationSets; + } + /** * @param property The property to check the required state for. * @return True when the property is required, false otherwise. diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/NavigationProperty.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/NavigationProperty.java index 252a3cc4b..0453f3900 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/NavigationProperty.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/NavigationProperty.java @@ -17,8 +17,8 @@ */ package de.fraunhofer.iosb.ilt.sta.path; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; /** @@ -27,42 +27,42 @@ */ public enum NavigationProperty implements Property { - Datastream(EntityType.Datastream, false), - Datastreams(EntityType.Datastream, true), - MultiDatastream(EntityType.MultiDatastream, false), - MultiDatastreams(EntityType.MultiDatastream, true), - FeatureOfInterest(EntityType.FeatureOfInterest, false), - HistoricalLocations(EntityType.HistoricalLocation, true), - Location(EntityType.Location, false), - Locations(EntityType.Location, true), - Observations(EntityType.Observation, true), - ObservedProperty(EntityType.ObservedProperty, false), - ObservedProperties(EntityType.ObservedProperty, true), - Sensor(EntityType.Sensor, false), - Thing(EntityType.Thing, false), - Things(EntityType.Thing, true); + DATASTREAM("Datastream", EntityType.DATASTREAM, false), + DATASTREAMS("Datastreams", EntityType.DATASTREAM, true), + MULTIDATASTREAM("MultiDatastream", EntityType.MULTIDATASTREAM, false), + MULTIDATASTREAMS("MultiDatastreams", EntityType.MULTIDATASTREAM, true), + FEATUREOFINTEREST("FeatureOfInterest", EntityType.FEATUREOFINTEREST, false), + HISTORICALLOCATIONS("HistoricalLocations", EntityType.HISTORICALLOCATION, true), + LOCATION("Location", EntityType.LOCATION, false), + LOCATIONS("Locations", EntityType.LOCATION, true), + OBSERVATIONS("Observations", EntityType.OBSERVATION, true), + OBSERVEDPROPERTY("ObservedProperty", EntityType.OBSERVEDPROPERTY, false), + OBSERVEDPROPERTIES("ObservedProperties", EntityType.OBSERVEDPROPERTY, true), + SENSOR("Sensor", EntityType.SENSOR, false), + THING("Thing", EntityType.THING, false), + THINGS("Things", EntityType.THING, true); private final Collection aliases; /** * The type of entity that this navigation property points to. */ public final EntityType type; + public final String propertyName; public final String getterName; public final String setterName; + public final String isSetName; public final boolean isSet; - private NavigationProperty(EntityType type, boolean isSet) { + private NavigationProperty(String propertyName, EntityType type, boolean isSet) { + this.propertyName = propertyName; this.aliases = new ArrayList<>(); - this.aliases.add(toString()); + this.aliases.add(propertyName); this.type = type; this.isSet = isSet; - this.getterName = "get" + name(); - this.setterName = "set" + name(); - } - - private NavigationProperty(EntityType type, boolean isSet, String... aliases) { - this(type, isSet); - this.aliases.addAll(Arrays.asList(aliases)); + String capitalized = StringHelper.capitalize(propertyName); + this.getterName = "get" + capitalized; + this.setterName = "set" + capitalized; + this.isSetName = "isSet" + capitalized; } public static NavigationProperty fromString(String propertyName) { @@ -82,7 +82,12 @@ public EntityType getType() { @Override public String getName() { - return name(); + return propertyName; + } + + @Override + public String getJsonName() { + return propertyName; } @Override @@ -95,4 +100,9 @@ public String getSetterName() { return setterName; } + @Override + public String getIsSetName() { + return isSetName; + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/Property.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/Property.java index e0f33f12d..edcd77306 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/Property.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/Property.java @@ -28,6 +28,11 @@ public interface Property { */ public String getName(); + /** + * @return The name of this property as used in JSON. + */ + public String getJsonName(); + /** * @return The name of the getter method for this property. */ @@ -37,4 +42,9 @@ public interface Property { * @return The name of the setter method for this property. */ public String getSetterName(); + + /** + * @return The name of the method to check if this property is set. + */ + public String getIsSetName(); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/PropertyPathElement.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/PropertyPathElement.java index e09bd1874..b50820cb6 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/PropertyPathElement.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/PropertyPathElement.java @@ -60,15 +60,12 @@ public void visit(ResourcePathVisitor visitor) { @Override public String toString() { - return property.name; + return property.entitiyName; } @Override public int hashCode() { - int hash = 3; - hash = 61 * hash + Objects.hashCode(this.property); - hash = 61 * hash + Objects.hashCode(this.parent); - return hash; + return Objects.hash(property, parent); } @Override @@ -80,13 +77,8 @@ public boolean equals(Object obj) { return false; } final PropertyPathElement other = (PropertyPathElement) obj; - if (this.property != other.property) { - return false; - } - if (!Objects.equals(this.parent, other.parent)) { - return false; - } - return true; + return this.property == other.property + && Objects.equals(this.parent, other.parent); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/ResourcePath.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/ResourcePath.java index c2d8e0cc0..af42270c7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/ResourcePath.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/path/ResourcePath.java @@ -43,6 +43,11 @@ public class ResourcePath { * Flag indicating there was a $value at the end of the path. */ private boolean value; + /** + * Flag indicating the path points to an entityProperty + * (EntitySet(id)/entityProperty). + */ + private boolean entityProperty; /** * The elements in this path. */ @@ -67,22 +72,92 @@ public ResourcePath(String serviceRootUrl, String pathUrl) { this.pathUrl = pathUrl; } + /** + * Flag indicating there was a $ref at the end of the path. + * + * @return the ref + */ public boolean isRef() { return ref; } + /** + * Flag indicating there was a $ref at the end of the path. + * + * @param ref the ref to set + */ + public void setRef(boolean ref) { + this.ref = ref; + } + + /** + * Flag indicating there was a $value at the end of the path. + * + * @return the value + */ public boolean isValue() { return value; } - public List getPathElements() { - return pathElements; + /** + * Flag indicating there was a $value at the end of the path. + * + * @param value the value to set + */ + public void setValue(boolean value) { + this.value = value; + } + + /** + * Flag indicating the path points to an entityProperty + * (EntitySet(id)/entityProperty). + * + * @return the entityProperty + */ + public boolean isEntityProperty() { + return entityProperty; + } + + /** + * Returns the number of elements in the path. + * + * @return The size of the path. + */ + public int size() { + return pathElements.size(); + } + + public boolean isEmpty() { + return pathElements.isEmpty(); + } + + /** + * Get the element with the given index, where the first element has index + * 0. + * + * @param index The index of the element to get. + * @return The element with the given index. + */ + public ResourcePathElement get(int index) { + return pathElements.get(index); } public ResourcePathElement getMainElement() { return mainElement; } + public EntityType getMainElementType() { + if (mainElement instanceof EntityPathElement) { + EntityPathElement entityPathElement = (EntityPathElement) mainElement; + return entityPathElement.getEntityType(); + } + if (mainElement instanceof EntitySetPathElement) { + EntitySetPathElement entitySetPathElement = (EntitySetPathElement) mainElement; + return entitySetPathElement.getEntityType(); + } + return null; + } + public ResourcePathElement getLastElement() { if (pathElements.isEmpty()) { return null; @@ -94,18 +169,6 @@ public EntityPathElement getIdentifiedElement() { return identifiedElement; } - public void setRef(boolean ref) { - this.ref = ref; - } - - public void setValue(boolean value) { - this.value = value; - } - - public void setPathElements(List pathElements) { - this.pathElements = pathElements; - } - public void setMainElement(ResourcePathElement mainElementType) { this.mainElement = mainElementType; } @@ -114,16 +177,38 @@ public void setIdentifiedElement(EntityPathElement identifiedElement) { this.identifiedElement = identifiedElement; } + /** + * Add the given element at the given index. + * + * @param index The position in the path to put the element. + * @param pe The element to add. + */ + public void addPathElement(int index, ResourcePathElement pe) { + pathElements.add(index, pe); + } + + public void addPathElement(ResourcePathElement pe) { + addPathElement(pe, false, false); + } + + /** + * Add the given path element, optionally setting it as the main element, or + * as the identifying element. + * + * @param pe The element to add. + * @param isMain Flag indicating it is the main element. + * @param isIdentifier Flag indicating it is the identifying element. + */ public void addPathElement(ResourcePathElement pe, boolean isMain, boolean isIdentifier) { - getPathElements().add(pe); - if (isMain && pe instanceof EntityPathElement) { - EntityPathElement epe = (EntityPathElement) pe; - setMainElement(epe); + pathElements.add(pe); + if (isMain && pe instanceof EntityPathElement || pe instanceof EntitySetPathElement) { + setMainElement(pe); } if (isIdentifier && pe instanceof EntityPathElement) { EntityPathElement epe = (EntityPathElement) pe; setIdentifiedElement(epe); } + this.entityProperty = (pe instanceof PropertyPathElement); } public void compress() { @@ -160,14 +245,7 @@ public void setPathUrl(String pathUrl) { @Override public int hashCode() { - int hash = 7; - hash = 97 * hash + Objects.hashCode(this.serviceRootUrl); - hash = 97 * hash + (this.ref ? 1 : 0); - hash = 97 * hash + (this.value ? 1 : 0); - hash = 97 * hash + Objects.hashCode(this.pathElements); - hash = 97 * hash + Objects.hashCode(this.mainElement); - hash = 97 * hash + Objects.hashCode(this.identifiedElement); - return hash; + return Objects.hash(serviceRootUrl, ref, value, pathElements, mainElement, identifiedElement); } @Override @@ -179,25 +257,12 @@ public boolean equals(Object obj) { return false; } final ResourcePath other = (ResourcePath) obj; - if (!Objects.equals(this.serviceRootUrl, other.serviceRootUrl)) { - return false; - } - if (this.ref != other.ref) { - return false; - } - if (this.value != other.value) { - return false; - } - if (!Objects.equals(this.pathElements, other.pathElements)) { - return false; - } - if (!Objects.equals(this.mainElement, other.mainElement)) { - return false; - } - if (!Objects.equals(this.identifiedElement, other.identifiedElement)) { - return false; - } - return true; + return Objects.equals(this.serviceRootUrl, other.serviceRootUrl) + && this.ref == other.ref + && this.value == other.value + && Objects.equals(this.pathElements, other.pathElements) + && Objects.equals(this.mainElement, other.mainElement) + && Objects.equals(this.identifiedElement, other.identifiedElement); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/AbstractPersistenceManager.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/AbstractPersistenceManager.java index d233e49ba..59442839e 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/AbstractPersistenceManager.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/AbstractPersistenceManager.java @@ -1,30 +1,37 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.persistence; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.messagebus.MessageBus; +import de.fraunhofer.iosb.ilt.sta.messagebus.MessageBusFactory; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; +import de.fraunhofer.iosb.ilt.sta.query.Query; import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; import java.util.ArrayList; import java.util.List; -import javax.swing.event.EventListenerList; /** * @@ -32,59 +39,39 @@ */ public abstract class AbstractPersistenceManager implements PersistenceManager { - protected EventListenerList entityChangeListeners = new EventListenerList(); - private final List insertedEntities; - private final List deletedEntities; - private final List updatedEntities; + /** + * The changed entity messages that need to be sent to the bus. + */ + private final List changedEntities; protected AbstractPersistenceManager() { - this.insertedEntities = new ArrayList<>(); - this.deletedEntities = new ArrayList<>(); - this.updatedEntities = new ArrayList<>(); + this.changedEntities = new ArrayList<>(); } - @Override - public void addEntityChangeListener(EntityChangeListener listener) { - entityChangeListeners.add(EntityChangeListener.class, listener); - } - - @Override - public void removeEntityChangeListener(EntityChangeListener listener) { - entityChangeListeners.remove(EntityChangeListener.class, listener); - } - - protected void fireEntityInserted(Entity e) { - Object[] listeners = entityChangeListeners.getListenerList(); - for (int i = 0; i < listeners.length; i = i + 2) { - if (listeners[i] == EntityChangeListener.class) { - ((EntityChangeListener) listeners[i + 1]).entityInserted(new EntityChangedEvent(this, null, e)); - } - } - } - - protected void fireEntityDeleted(Entity e) { - Object[] listeners = entityChangeListeners.getListenerList(); - for (int i = 0; i < listeners.length; i = i + 2) { - if (listeners[i] == EntityChangeListener.class) { - ((EntityChangeListener) listeners[i + 1]).entityDeleted(new EntityChangedEvent(this, null, e)); - } - } - } - - protected void fireEntityUpdated(Entity oldEntity, Entity newEntity) { - Object[] listeners = entityChangeListeners.getListenerList(); - for (int i = 0; i < listeners.length; i = i + 2) { - if (listeners[i] == EntityChangeListener.class) { - ((EntityChangeListener) listeners[i + 1]).entityUpdated(new EntityChangedEvent(this, oldEntity, newEntity)); + private Entity fetchEntity(EntityType entityType, Id id) { + Entity entity = get(entityType, id); + for (NavigationProperty property : entityType.getNavigationEntities()) { + Object parentObject = entity.getProperty(property); + if (parentObject instanceof Entity) { + Entity parentEntity = (Entity) parentObject; + parentEntity.setExportObject(true); } } + return entity; } @Override public boolean insert(Entity entity) throws NoSuchEntityException, IncompleteEntityException { boolean result = doInsert(entity); if (result) { - insertedEntities.add(entity); + Entity newEntity = fetchEntity( + entity.getEntityType(), + entity.getId()); + changedEntities.add( + new EntityChangedMessage() + .setEventType(EntityChangedMessage.Type.CREATE) + .setEntity(newEntity) + ); } return result; } @@ -96,11 +83,20 @@ public boolean delete(EntityPathElement pathElement) throws NoSuchEntityExceptio Entity entity = getEntityByEntityPath(pathElement); boolean result = doDelete(pathElement); if (result) { - deletedEntities.add(entity); + changedEntities.add( + new EntityChangedMessage() + .setEventType(EntityChangedMessage.Type.DELETE) + .setEntity(entity) + ); } return result; } + @Override + public void delete(ResourcePath path, Query query) throws NoSuchEntityException { + doDelete(path, query); + } + private Entity getEntityByEntityPath(EntityPathElement pathElement) { ResourcePath path = new ResourcePath(); path.addPathElement(new EntitySetPathElement(pathElement.getEntityType(), null), false, false); @@ -111,29 +107,48 @@ private Entity getEntityByEntityPath(EntityPathElement pathElement) { public abstract boolean doDelete(EntityPathElement pathElement) throws NoSuchEntityException; + public abstract void doDelete(ResourcePath path, Query query); + @Override - public boolean update(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException { - Entity oldEntity = getEntityByEntityPath(pathElement); - boolean result = doUpdate(pathElement, entity); - if (result) { - updatedEntities.add(new EntityUpdateInfo(oldEntity, getEntityByEntityPath(pathElement))); + public boolean update(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException, IncompleteEntityException { + EntityChangedMessage result = doUpdate(pathElement, entity); + if (result != null) { + result.setEventType(EntityChangedMessage.Type.UPDATE); + Entity newEntity = fetchEntity( + entity.getEntityType(), + entity.getId()); + result.setEntity(newEntity); + changedEntities.add(result); } - return result; - } - - public abstract boolean doUpdate(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException; - + return result != null; + } + + /** + * Update the given entity and return a message with the fields that were + * changed. The entity is added to the message by the + * AbstractPersistenceManager. + * + * @param pathElement The path to the entity to update. + * @param entity The updated entity. + * @return A message with the fields that were changed. The entity is added + * by the AbstractPersistenceManager. + * @throws NoSuchEntityException If the entity does not exist. + * @throws IncompleteEntityException If the entity does not have all the + * required fields. + */ + public abstract EntityChangedMessage doUpdate(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException, IncompleteEntityException; + + /** + * If there are changes to send, connect to bus and send them. + */ private void fireEntityChangeEvents() { - insertedEntities.forEach(e -> fireEntityInserted(e)); - deletedEntities.forEach(e -> fireEntityDeleted(e)); - updatedEntities.forEach(e -> fireEntityUpdated(e.oldEntity, e.newEntity)); + MessageBus messageBus = MessageBusFactory.getMessageBus(); + changedEntities.forEach(messageBus::sendMessage); clearEntityChangedEvents(); } private void clearEntityChangedEvents() { - insertedEntities.clear(); - deletedEntities.clear(); - updatedEntities.clear(); + changedEntities.clear(); } @Override @@ -162,15 +177,4 @@ public void close() { } protected abstract boolean doClose(); - - private class EntityUpdateInfo { - - Entity oldEntity; - Entity newEntity; - - public EntityUpdateInfo(Entity oldEntity, Entity newEntity) { - this.oldEntity = oldEntity; - this.newEntity = newEntity; - } - } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/BasicPersistenceType.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/BasicPersistenceType.java index 8ce938186..0ab3b5d22 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/BasicPersistenceType.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/BasicPersistenceType.java @@ -22,8 +22,8 @@ * @author jab */ public enum BasicPersistenceType { - Integer, - Double, - String, - ByteArray + INTEGER, + DOUBLE, + STRING, + BYTEARRAY } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/EntityChangedEvent.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/EntityChangedEvent.java deleted file mode 100644 index 8b0062c6c..000000000 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/EntityChangedEvent.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence; - -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import java.util.EventObject; - -/** - * - * @author jab - */ -public class EntityChangedEvent extends EventObject { - - private final Entity oldEntity; - private final Entity newEntity; - - public EntityChangedEvent(PersistenceManager source, Entity oldEntity, Entity newEntity) { - super(source); - this.oldEntity = oldEntity; - this.newEntity = newEntity; - } - - @Override - public PersistenceManager getSource() { - return (PersistenceManager) source; - } - - public Entity getOldEntity() { - return oldEntity; - } - - public Entity getNewEntity() { - return newEntity; - } - -} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManager.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManager.java index 7908d4ba4..7cf7a71ad 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManager.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManager.java @@ -17,46 +17,34 @@ */ package de.fraunhofer.iosb.ilt.sta.persistence; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; -import de.fraunhofer.iosb.ilt.sta.model.id.StringId; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; /** * * @author scf */ -public interface IdManager { +public interface IdManager { + /** + * Get the Id implementation used by this IdManager. + * + * @return The Class that implements Id. + */ public Class getIdClass(); + /** + * Parse the given input and generate an Id from it. + * + * @param input The input to parse as Id. + * @return The Id. + */ public Id parseId(String input); - public static final IdManager ID_MANAGER_LONG = new IdManager() { - @Override - public Class getIdClass() { - return LongId.class; - } - - @Override - public Id parseId(String input) { - return new LongId(Long.parseLong(input)); - } - }; - - public static final IdManager ID_MANAGER_STRING = new IdManager() { - @Override - public Class getIdClass() { - return StringId.class; - } - - @Override - public Id parseId(String input) { - if (input.startsWith("'")) { - return new StringId(input.substring(1, input.length() - 1)); - } - return new StringId(input); - } - }; -; - + /** + * Wrap the given id object in an Id. + * + * @param input The id object to wrap. + * @return an Id. + */ + public Id fromObject(T input); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManagerString.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManagerString.java new file mode 100644 index 000000000..434d0d8af --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManagerString.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 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.sta.persistence; + +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.IdString; + +/** + * + * @author scf + */ +public class IdManagerString implements IdManager { + + @Override + public Class getIdClass() { + return IdString.class; + } + + @Override + public Id parseId(String input) { + if (input.startsWith("'")) { + String idString = input.substring(1, input.length() - 1); + idString = idString.replaceAll("''", "'"); + return new IdString(idString); + } + return new IdString(input); + } + + @Override + public Id fromObject(Object input) { + return new IdString(input.toString()); + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManagerlong.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManagerlong.java new file mode 100644 index 000000000..296bce1d6 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/IdManagerlong.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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.sta.persistence; + +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.IdLong; + +/** + * + * @author scf + */ +public class IdManagerlong implements IdManager { + + @Override + public Class getIdClass() { + return IdLong.class; + } + + @Override + public Id parseId(String input) { + return new IdLong(Long.parseLong(input)); + } + + @Override + public Id fromObject(Object input) { + if (input instanceof Number) { + return new IdLong(((Number) input).longValue()); + } + throw new IllegalArgumentException("Can not use " + input.getClass().getName() + " (" + input + ") as a long Id"); + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManager.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManager.java index d5741ed9a..ec20b74a8 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManager.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManager.java @@ -18,16 +18,17 @@ package de.fraunhofer.iosb.ilt.sta.persistence; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.query.Query; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import de.fraunhofer.iosb.ilt.sta.util.UpgradeFailedException; +import java.io.IOException; +import java.io.Writer; /** * @@ -56,15 +57,9 @@ public interface PersistenceManager { */ public boolean insert(Entity entity) throws NoSuchEntityException, IncompleteEntityException; - public Object get(ResourcePath path, Query query); + public Entity get(EntityType entityType, Id id); - public default Entity getEntityById(String serviceRootUrl, EntityType entityType, Id id) { - ResourcePath path = new ResourcePath(); - path.addPathElement(new EntitySetPathElement(entityType, null), false, false); - path.addPathElement(new EntityPathElement(id, entityType, path.getLastElement()), true, true); - path.setServiceRootUrl(serviceRootUrl); - return (Entity) get(path, null); - } + public Object get(ResourcePath path, Query query); public default T get(ResourcePath path, Query query, Class clazz) { Object result = get(path, query); @@ -76,11 +71,27 @@ public default T get(ResourcePath path, Query query, Class clazz) { public boolean delete(EntityPathElement pathElement) throws NoSuchEntityException; - public boolean update(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException; - - public void addEntityChangeListener(EntityChangeListener listener); + /** + * Delete all entities in the given path, matching the filter in the given + * query. + * + * @param path The path to an entity set. + * @param query The query containing only a filter. + * @throws NoSuchEntityException If the path does not lead to an entity set. + */ + public void delete(ResourcePath path, Query query) throws NoSuchEntityException; - public void removeEntityChangeListener(EntityChangeListener listener); + /** + * Update the given entity. + * + * @param pathElement The path to the entity. + * @param entity The entity. + * @return True if the update was successful. + * @throws NoSuchEntityException If the entity does not exist. + * @throws IncompleteEntityException If the given entity is missing required + * fields. + */ + public boolean update(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException, IncompleteEntityException; /** * Initialise using the given settings. @@ -124,7 +135,12 @@ public default void commitAndClose() { /** * Upgrade the storage backend. * - * @return a log of what was done. + * @param out The Writer to append logging messages to. + * @return true if the upgrade was successful, false if upgrade should be + * tried again later. + * @throws de.fraunhofer.iosb.ilt.sta.util.UpgradeFailedException when + * upgrading fails and should not be attempted again at a later stage. + * @throws java.io.IOException when the Writer throws this exception. */ - public String doUpgrades(); + public boolean doUpgrades(Writer out) throws UpgradeFailedException, IOException; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManagerFactory.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManagerFactory.java index 53d38b694..f9e7aaa2c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManagerFactory.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/PersistenceManagerFactory.java @@ -1,23 +1,27 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.persistence; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; -import javax.swing.event.EventListenerList; +import de.fraunhofer.iosb.ilt.sta.settings.PersistenceSettings; +import de.fraunhofer.iosb.ilt.sta.util.UpgradeFailedException; +import java.io.IOException; +import java.io.StringWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,22 +32,19 @@ public class PersistenceManagerFactory { private static final String ERROR_MSG = "Could not generate PersistenceManager instance: "; - private static final EventListenerList entityChangeListeners = new EventListenerList(); + private static final Logger LOGGER = LoggerFactory.getLogger(PersistenceManagerFactory.class); private static PersistenceManagerFactory instance; + private static boolean maybeUpdateDatabase = true; public static synchronized void init(CoreSettings settings) { if (instance == null) { instance = new PersistenceManagerFactory(settings); + maybeUpdateDatabase(settings, instance); + } + if (maybeUpdateDatabase) { + maybeUpdateDatabase(settings, instance); } - } - - public static void addEntityChangeListener(EntityChangeListener listener) { - entityChangeListeners.add(EntityChangeListener.class, listener); - } - - public static void removeEntityChangeListener(EntityChangeListener listener) { - entityChangeListeners.remove(EntityChangeListener.class, listener); } public static PersistenceManagerFactory getInstance() { @@ -77,12 +78,36 @@ public PersistenceManager create() { try { persistenceManager = (PersistenceManager) persistenceManagerClass.newInstance(); persistenceManager.init(settings); - for (EntityChangeListener listener : entityChangeListeners.getListeners(EntityChangeListener.class)) { - persistenceManager.addEntityChangeListener(listener); - } } catch (InstantiationException | IllegalAccessException ex) { LOGGER.error(ERROR_MSG + "Class '" + settings.getPersistenceSettings().getPersistenceManagerImplementationClass() + "' could not be instantiated", ex); } return persistenceManager; } + + private static void maybeUpdateDatabase(CoreSettings coreSettings, PersistenceManagerFactory instance) { + PersistenceSettings persistenceSettings = coreSettings.getPersistenceSettings(); + if (persistenceSettings.isAutoUpdateDatabase()) { + StringWriter updateLog = new StringWriter(); + try { + boolean success = instance.create().doUpgrades(updateLog); + maybeUpdateDatabase = !success; + if (success) { + LOGGER.info("Database-update successful."); + } else { + LOGGER.info("Database-update not successful, trying again later."); + } + } catch (UpgradeFailedException ex) { + LOGGER.error("Database upgrade failed.", ex); + maybeUpdateDatabase = false; + } catch (IOException ex) { + // Should not happen, StringWriter does not throw IOExceptions. + LOGGER.error("Database upgrade failed.", ex); + } + String logString = updateLog.toString(); + if (!logString.isEmpty()) { + LOGGER.info("Database-update-log:\n{}", logString); + } + } + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Expand.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Expand.java index 7c4ff4a7d..5404caf88 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Expand.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Expand.java @@ -18,8 +18,6 @@ package de.fraunhofer.iosb.ilt.sta.query; import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; @@ -75,15 +73,7 @@ public void validate(ResourcePath path) { if (mainElement instanceof PropertyPathElement || mainElement instanceof CustomPropertyPathElement) { throw new IllegalArgumentException("No expand allowed on property paths."); } - EntityType entityType = null; - if (mainElement instanceof EntityPathElement) { - EntityPathElement entityPathElement = (EntityPathElement) mainElement;; - entityType = entityPathElement.getEntityType(); - } - if (mainElement instanceof EntitySetPathElement) { - EntitySetPathElement entitySetPathElement = (EntitySetPathElement) mainElement; - entityType = entitySetPathElement.getEntityType(); - } + EntityType entityType = path.getMainElementType(); if (entityType == null) { throw new IllegalStateException("Unkown ResourcePathElementType found."); } @@ -94,7 +84,7 @@ protected void validate(EntityType entityType) { EntityType currentEntityType = entityType; for (NavigationProperty navigationProperty : this.path) { if (!currentEntityType.getPropertySet().contains(navigationProperty)) { - throw new IllegalArgumentException("Invalid expand path '" + navigationProperty.getName() + "' on entity type " + currentEntityType.name); + throw new IllegalArgumentException("Invalid expand path '" + navigationProperty.getName() + "' on entity type " + currentEntityType.entityName); } currentEntityType = navigationProperty.getType(); } @@ -105,10 +95,7 @@ protected void validate(EntityType entityType) { @Override public int hashCode() { - int hash = 5; - hash = 83 * hash + Objects.hashCode(this.path); - hash = 83 * hash + Objects.hashCode(this.subQuery); - return hash; + return Objects.hash(path, subQuery); } @Override @@ -123,13 +110,8 @@ public boolean equals(Object obj) { return false; } final Expand other = (Expand) obj; - if (!Objects.equals(this.path, other.path)) { - return false; - } - if (!Objects.equals(this.subQuery, other.subQuery)) { - return false; - } - return true; + return Objects.equals(this.path, other.path) + && Objects.equals(this.subQuery, other.subQuery); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/OrderBy.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/OrderBy.java index 139a415d9..8a8d05063 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/OrderBy.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/OrderBy.java @@ -17,9 +17,8 @@ */ package de.fraunhofer.iosb.ilt.sta.query; -import java.util.Objects; - import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; +import java.util.Objects; /** * @@ -27,14 +26,21 @@ */ public class OrderBy { - public static enum OrderType { + /** + * The two directions that can be used for sorting. + */ + public enum OrderType { + + ASCENDING("asc"), + DESCENDING("desc"); - Ascending("asc"), - Descending("desc"); - public final String name; + /** + * The direction as it appears in the url. + */ + public final String direction; - private OrderType(String name) { - this.name = name; + private OrderType(String direction) { + this.direction = direction; } } @@ -48,7 +54,7 @@ public OrderBy(Expression expression, OrderType type) { public OrderBy(Expression expression) { this.expression = expression; - this.type = OrderType.Ascending; + this.type = OrderType.ASCENDING; } public Expression getExpression() { @@ -61,10 +67,7 @@ public OrderType getType() { @Override public int hashCode() { - int hash = 7; - hash = 97 * hash + Objects.hashCode(this.expression); - hash = 97 * hash + Objects.hashCode(this.type); - return hash; + return Objects.hash(expression, type); } @Override @@ -79,18 +82,13 @@ public boolean equals(Object obj) { return false; } final OrderBy other = (OrderBy) obj; - if (!Objects.equals(this.expression, other.expression)) { - return false; - } - if (this.type != other.type) { - return false; - } - return true; + return Objects.equals(this.expression, other.expression) + && this.type == other.type; } @Override public String toString() { - return expression.toUrl() + " " + type.name; + return expression.toUrl() + " " + type.direction; } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Query.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Query.java index 7e782b17f..7f542907d 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Query.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/Query.java @@ -18,8 +18,6 @@ package de.fraunhofer.iosb.ilt.sta.query; import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; @@ -28,16 +26,12 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * @@ -45,10 +39,6 @@ */ public class Query { - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(Query.class); private CoreSettings settings; private Optional top; private Optional skip; @@ -78,15 +68,7 @@ public void validate(ResourcePath path) { if (mainElement instanceof PropertyPathElement || mainElement instanceof CustomPropertyPathElement) { throw new IllegalArgumentException("No queries allowed for property paths."); } - EntityType entityType = null; - if (mainElement instanceof EntityPathElement) { - EntityPathElement entityPathElement = (EntityPathElement) mainElement;; - entityType = entityPathElement.getEntityType(); - } - if (mainElement instanceof EntitySetPathElement) { - EntitySetPathElement entitySetPathElement = (EntitySetPathElement) mainElement; - entityType = entitySetPathElement.getEntityType(); - } + EntityType entityType = path.getMainElementType(); if (entityType == null) { throw new IllegalStateException("Unkown ResourcePathElementType found."); } @@ -97,7 +79,7 @@ protected void validate(EntityType entityType) { Set propertySet = entityType.getPropertySet(); Optional invalidProperty = select.stream().filter(x -> !propertySet.contains(x)).findAny(); if (invalidProperty.isPresent()) { - throw new IllegalArgumentException("Invalid property '" + invalidProperty.get().getName() + "' found for entity type " + entityType.name); + throw new IllegalArgumentException("Invalid property '" + invalidProperty.get().getName() + "' found for entity type " + entityType.entityName); } expand.forEach(x -> x.validate(entityType)); } @@ -206,16 +188,7 @@ public void setOrderBy(List orderBy) { @Override public int hashCode() { - int hash = 7; - hash = 73 * hash + Objects.hashCode(this.top); - hash = 73 * hash + Objects.hashCode(this.skip); - hash = 73 * hash + Objects.hashCode(this.count); - hash = 73 * hash + Objects.hashCode(this.select); - hash = 73 * hash + Objects.hashCode(this.filter); - hash = 73 * hash + Objects.hashCode(this.format); - hash = 73 * hash + Objects.hashCode(this.expand); - hash = 73 * hash + Objects.hashCode(this.orderBy); - return hash; + return Objects.hash(top, skip, count, select, filter, format, expand, orderBy); } @Override @@ -230,31 +203,14 @@ public boolean equals(Object obj) { return false; } final Query other = (Query) obj; - if (!Objects.equals(this.count, other.count)) { - return false; - } - if (!Objects.equals(this.top, other.top)) { - return false; - } - if (!Objects.equals(this.skip, other.skip)) { - return false; - } - if (!Objects.equals(this.select, other.select)) { - return false; - } - if (!Objects.equals(this.filter, other.filter)) { - return false; - } - if (!Objects.equals(this.format, other.format)) { - return false; - } - if (!Objects.equals(this.expand, other.expand)) { - return false; - } - if (!Objects.equals(this.orderBy, other.orderBy)) { - return false; - } - return true; + return Objects.equals(this.count, other.count) + && Objects.equals(this.top, other.top) + && Objects.equals(this.skip, other.skip) + && Objects.equals(this.select, other.select) + && Objects.equals(this.filter, other.filter) + && Objects.equals(this.format, other.format) + && Objects.equals(this.expand, other.expand) + && Objects.equals(this.orderBy, other.orderBy); } @Override @@ -266,39 +222,73 @@ public String toString(boolean inExpand) { char separator = inExpand ? ';' : '&'; StringBuilder sb = new StringBuilder(); - if (top.isPresent()) { - sb.append(separator).append("$top=").append(top.get()); + + addTopToUrl(sb, separator); + + addSkipToUrl(sb, separator); + + addSelectToUrl(sb, separator); + + addFilterToUrl(sb, separator, inExpand); + + addFormatToUrl(sb, separator); + + addExpandToUrl(sb, separator, inExpand); + + addOrderbyToUrl(sb, separator, inExpand); + + addCountToUrl(sb, separator); + + if (sb.length() > 0) { + return sb.substring(1); + } + return ""; + } + + private void addCountToUrl(StringBuilder sb, char separator) { + if (count.isPresent()) { + sb.append(separator).append("$count=").append(count.get()); + } + } + + private void addFormatToUrl(StringBuilder sb, char separator) { + if (format != null) { + sb.append(separator).append("$resultFormat=").append(UrlHelper.urlEncode(format)); } + } + + private void addSkipToUrl(StringBuilder sb, char separator) { if (skip.isPresent()) { sb.append(separator).append("$skip=").append(skip.get()); } - if (!select.isEmpty()) { - sb.append(separator).append("$select="); + } + + private void addTopToUrl(StringBuilder sb, char separator) { + if (top.isPresent()) { + sb.append(separator).append("$top=").append(top.get()); + } + } + + private void addOrderbyToUrl(StringBuilder sb, char separator, boolean inExpand) { + if (!orderBy.isEmpty()) { + sb.append(separator).append("$orderby="); boolean firstDone = false; - for (Property property : select) { + for (OrderBy ob : orderBy) { if (firstDone) { sb.append(","); } else { firstDone = true; } - try { - sb.append(URLEncoder.encode(property.getName(), "UTF-8")); - } catch (UnsupportedEncodingException ex) { - LOGGER.error("UTF-8 not supported?!", ex); + String orderUrl = ob.toString(); + if (!inExpand) { + orderUrl = UrlHelper.urlEncode(orderUrl); } + sb.append(orderUrl); } } - if (filter != null) { - sb.append(separator).append("$filter="); - String filterUrl = filter.toUrl(); - if (!inExpand) { - filterUrl = UrlHelper.urlEncode(filterUrl); - } - sb.append(filterUrl); - } - if (format != null) { - sb.append(separator).append("$resultFormat=").append(UrlHelper.urlEncode(format)); - } + } + + private void addExpandToUrl(StringBuilder sb, char separator, boolean inExpand) { if (!expand.isEmpty()) { sb.append(separator).append("$expand="); boolean firstDone = false; @@ -315,29 +305,32 @@ public String toString(boolean inExpand) { sb.append(expandUrl); } } - if (!orderBy.isEmpty()) { - sb.append(separator).append("$orderby="); + } + + private void addFilterToUrl(StringBuilder sb, char separator, boolean inExpand) { + if (filter != null) { + sb.append(separator).append("$filter="); + String filterUrl = filter.toUrl(); + if (!inExpand) { + filterUrl = UrlHelper.urlEncode(filterUrl); + } + sb.append(filterUrl); + } + } + + private void addSelectToUrl(StringBuilder sb, char separator) { + if (!select.isEmpty()) { + sb.append(separator).append("$select="); boolean firstDone = false; - for (OrderBy ob : orderBy) { + for (Property property : select) { if (firstDone) { sb.append(","); } else { firstDone = true; } - String orderUrl = ob.toString(); - if (!inExpand) { - orderUrl = UrlHelper.urlEncode(orderUrl); - } - sb.append(orderUrl); + sb.append(UrlHelper.urlEncode(property.getName())); } } - if (count.isPresent()) { - sb.append(separator).append("$count=").append(count.get()); - } - if (sb.length() > 0) { - return sb.substring(1); - } - return ""; } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/ExpressionVisitor.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/ExpressionVisitor.java index 8b21b7057..a4eab4240 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/ExpressionVisitor.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/ExpressionVisitor.java @@ -53,9 +53,9 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.Time; import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.TotalOffsetMinutes; import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.Year; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoDistance; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoIntersects; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoLength; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoDistance; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoIntersects; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoLength; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.And; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.Not; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.Or; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/Path.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/Path.java index 8af01ad87..0da872b95 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/Path.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/Path.java @@ -49,9 +49,7 @@ public List getElements() { @Override public int hashCode() { - int hash = 5; - hash = 89 * hash + Objects.hashCode(this.elements); - return hash; + return Objects.hash(elements); } @Override @@ -66,10 +64,7 @@ public boolean equals(Object obj) { return false; } final Path other = (Path) obj; - if (!Objects.equals(this.elements, other.elements)) { - return false; - } - return true; + return Objects.equals(this.elements, other.elements); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/Constant.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/Constant.java index b14b6cd42..ec128749c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/Constant.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/Constant.java @@ -65,13 +65,6 @@ public T getValue() { return value; } - @Override - public int hashCode() { - int hash = 3; - hash = 83 * hash + Objects.hashCode(this.value); - return hash; - } - @Override public boolean equals(Object obj) { if (this == obj) { @@ -84,10 +77,12 @@ public boolean equals(Object obj) { return false; } final Constant other = (Constant) obj; - if (!Objects.equals(this.value, other.value)) { - return false; - } - return true; + return Objects.equals(this.value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/GeoJsonConstant.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/GeoJsonConstant.java index aa34f9ee7..a4a1e3b19 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/GeoJsonConstant.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/GeoJsonConstant.java @@ -17,11 +17,13 @@ */ package de.fraunhofer.iosb.ilt.sta.query.expression.constant; +import java.util.Objects; import org.geojson.GeoJsonObject; /** * * @author jab + * @param */ public abstract class GeoJsonConstant extends Constant { @@ -56,11 +58,6 @@ protected GeoJsonConstant(String value) { this.value = parse(value); } - @Override - public T getValue() { - return super.getValue(); - } - /** * The WKT that generated this geometry. * @@ -85,4 +82,25 @@ public String toUrl() { } protected abstract T parse(String value); + + @Override + public int hashCode() { + return Objects.hash(source); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final GeoJsonConstant other = (GeoJsonConstant) obj; + return Objects.equals(this.source, other.source); + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/NumericConstant.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/NumericConstant.java index b150d96a4..4456a7d48 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/NumericConstant.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/constant/NumericConstant.java @@ -28,11 +28,6 @@ public NumericConstant(T value) { super(value); } - @Override - public T getValue() { - return super.getValue(); //To change body of generated methods, choose Tools | Templates. - } - @Override public String toUrl() { return getValue().toString(); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/Function.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/Function.java index 2207f5ece..7d57e7711 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/Function.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/Function.java @@ -17,14 +17,6 @@ */ package de.fraunhofer.iosb.ilt.sta.query.expression.function; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; import de.fraunhofer.iosb.ilt.sta.query.expression.constant.BooleanConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.constant.Constant; @@ -37,6 +29,15 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.constant.PointConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.constant.PolygonConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.constant.StringConstant; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Represents a function of the API which can be used in a query. @@ -45,6 +46,11 @@ */ public abstract class Function implements Expression { + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(Function.class); + protected List parameters; protected List allowedTypeBindings; private final String functionName; @@ -60,6 +66,11 @@ public Function(Expression... parameters) { functionName = getClass().getSimpleName().toLowerCase(); } + public Function(String functionName) { + this.functionName = functionName; + allowedTypeBindings = new ArrayList(); + } + public Function(String functionName, Expression... parameters) { this.functionName = functionName; this.parameters = Arrays.asList(parameters); @@ -108,13 +119,18 @@ public List getParameters() { */ @Override public final Expression compress() { - return eval(parameters.stream().map(x -> x.compress()).collect(Collectors.toList())); + return eval( + parameters + .stream() + .map(Expression::compress) + .collect(Collectors.toList()) + ); } /** * Searches in dynamic type for a method with name 'eval' and suitable * parameters and then invokes it. If there is no suitable method or calling - * the method fails this is returned. + * the method fails, 'this' is returned. * * @param parameters Parameters to invoke the function on * @return The result of the evaluation. Should be subclass of Constant for @@ -123,16 +139,12 @@ public final Expression compress() { private Expression eval(List parameters) { try { // getDeclaredMethod not working with inheritance, must find suited method myself - //Method method = this.getClass().getDeclaredMethod("eval", parameters.stream().map(x -> x.getClass()).toArray(size -> new Class[size])); Method method = findMethod(parameters); if (method != null) { - if (!method.isAccessible()) { - method.setAccessible(true); - } return (Expression) method.invoke(this, parameters.toArray()); } } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - System.err.println(ex); + LOGGER.info("Could not eval.", ex); } return this; } @@ -141,27 +153,25 @@ private Method findMethod(List parameters) { Method[] methods = getClass().getDeclaredMethods(); List suitableMethods = new ArrayList<>(); for (Method method : methods) { - if (method.getName().equals("eval")) { - if (parameters.size() != method.getParameterCount()) { - continue; - } - Class[] parameterTypes = method.getParameterTypes(); - boolean assignable = true; - for (int i = 0; i < parameters.size(); i++) { - if (!parameterTypes[i].isAssignableFrom(parameters.get(i).getClass())) { - assignable = false; - break; - } - } - if (assignable) { - suitableMethods.add(method); + if (!method.getName().equals("eval") || parameters.size() != method.getParameterCount()) { + continue; + } + Class[] parameterTypes = method.getParameterTypes(); + boolean assignable = true; + for (int i = 0; i < parameters.size(); i++) { + if (!parameterTypes[i].isAssignableFrom(parameters.get(i).getClass())) { + assignable = false; + break; } } + if (assignable) { + suitableMethods.add(method); + } } if (suitableMethods.size() > 1) { // we need to find the most specific method. This is done argument by argument but not now } - if (suitableMethods.size() > 0) { + if (!suitableMethods.isEmpty()) { return suitableMethods.get(0); } return null; @@ -178,10 +188,7 @@ public List getAllowedTypeBindings() { @Override public int hashCode() { - int hash = 5; - hash = 67 * hash + Objects.hashCode(this.parameters); - hash = 67 * hash + Objects.hashCode(this.allowedTypeBindings); - return hash; + return Objects.hash(parameters, allowedTypeBindings); } @Override @@ -196,13 +203,8 @@ public boolean equals(Object obj) { return false; } final Function other = (Function) obj; - if (!Objects.equals(this.parameters, other.parameters)) { - return false; - } - if (!Objects.equals(this.getAllowedTypeBindings(), other.getAllowedTypeBindings())) { - return false; - } - return true; + return Objects.equals(parameters, other.parameters) + && Objects.equals(getAllowedTypeBindings(), other.getAllowedTypeBindings()); } protected static List getTypeBindingForAllTypes() { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/FunctionTypeBinding.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/FunctionTypeBinding.java index 8cfa4b227..57362a86c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/FunctionTypeBinding.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/FunctionTypeBinding.java @@ -46,10 +46,7 @@ public Class getReturnType() { @Override public int hashCode() { - int hash = 7; - hash = 67 * hash + Objects.hashCode(this.parameters); - hash = 67 * hash + Objects.hashCode(this.returnType); - return hash; + return Objects.hash(parameters, returnType); } @Override @@ -64,13 +61,8 @@ public boolean equals(Object obj) { return false; } final FunctionTypeBinding other = (FunctionTypeBinding) obj; - if (!Objects.equals(this.parameters, other.parameters)) { - return false; - } - if (!Objects.equals(this.returnType, other.returnType)) { - return false; - } - return true; + return Objects.equals(this.parameters, other.parameters) + && Objects.equals(this.returnType, other.returnType); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/Utils.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/Utils.java new file mode 100644 index 000000000..426e29e52 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/Utils.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 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.sta.query.expression.function; + +import de.fraunhofer.iosb.ilt.sta.query.expression.constant.DoubleConstant; +import de.fraunhofer.iosb.ilt.sta.query.expression.constant.IntegerConstant; +import java.util.List; + +/** + * + * @author scf + */ +public class Utils { + + private Utils() { + // Utility class, not to be instantiated. + } + + public static void allowTypeBindingsCommonNumbers(List allowedTypeBindings) { + allowedTypeBindings.add(new FunctionTypeBinding(IntegerConstant.class, IntegerConstant.class, IntegerConstant.class)); + allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, DoubleConstant.class)); + allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, IntegerConstant.class, DoubleConstant.class)); + allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, IntegerConstant.class)); + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Add.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Add.java index 9188d53e0..29fce4523 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Add.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Add.java @@ -27,6 +27,7 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.constant.NumericConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.function.Function; import de.fraunhofer.iosb.ilt.sta.query.expression.function.FunctionTypeBinding; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.Utils; /** * @@ -35,13 +36,14 @@ public class Add extends Function { public Add() { + // Parameters added later... } public Add(Expression... parameters) { super(parameters); } - protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { + protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { Number n1 = p1.getValue(); Number n2 = p2.getValue(); if (n1 instanceof Double || n2 instanceof Double) { @@ -53,13 +55,11 @@ protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { @Override protected void initAllowedTypeBindings() { + Utils.allowTypeBindingsCommonNumbers(allowedTypeBindings); + allowedTypeBindings.add(new FunctionTypeBinding(DateTimeConstant.class, DateTimeConstant.class, DurationConstant.class)); allowedTypeBindings.add(new FunctionTypeBinding(DurationConstant.class, DurationConstant.class, DurationConstant.class)); allowedTypeBindings.add(new FunctionTypeBinding(DateTimeConstant.class, DateConstant.class, DurationConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(IntegerConstant.class, IntegerConstant.class, IntegerConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, IntegerConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, IntegerConstant.class)); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Divide.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Divide.java index ebd1412e7..8b8a8cbbd 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Divide.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Divide.java @@ -23,7 +23,7 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.constant.IntegerConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.constant.NumericConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.function.Function; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.FunctionTypeBinding; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.Utils; /** * @@ -32,13 +32,14 @@ public class Divide extends Function { public Divide() { + // Parameters added later... } public Divide(Expression... parameters) { super(parameters); } - protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { + protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { Number n1 = p1.getValue(); Number n2 = p2.getValue(); if (n1 instanceof Double || n2 instanceof Double) { @@ -50,10 +51,7 @@ protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { @Override protected void initAllowedTypeBindings() { - allowedTypeBindings.add(new FunctionTypeBinding(IntegerConstant.class, IntegerConstant.class, IntegerConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, IntegerConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, IntegerConstant.class)); + Utils.allowTypeBindingsCommonNumbers(allowedTypeBindings); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Modulo.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Modulo.java index fb4f12f7f..66ed058d0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Modulo.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Modulo.java @@ -23,7 +23,7 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.constant.IntegerConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.constant.NumericConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.function.Function; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.FunctionTypeBinding; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.Utils; /** * @@ -32,13 +32,14 @@ public class Modulo extends Function { public Modulo() { + // Parameters added later... } public Modulo(Expression... parameters) { super(parameters); } - protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { + protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { Number n1 = p1.getValue(); Number n2 = p2.getValue(); if (n1 instanceof Double || n2 instanceof Double) { @@ -50,10 +51,7 @@ protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { @Override protected void initAllowedTypeBindings() { - allowedTypeBindings.add(new FunctionTypeBinding(IntegerConstant.class, IntegerConstant.class, IntegerConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, IntegerConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, IntegerConstant.class)); + Utils.allowTypeBindingsCommonNumbers(allowedTypeBindings); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Multiply.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Multiply.java index dade99b1f..e04785b56 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Multiply.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Multiply.java @@ -23,7 +23,7 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.constant.IntegerConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.constant.NumericConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.function.Function; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.FunctionTypeBinding; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.Utils; /** * @@ -32,13 +32,14 @@ public class Multiply extends Function { public Multiply() { + // Parameters added later... } public Multiply(Expression... parameters) { super(parameters); } - protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { + protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { Number n1 = p1.getValue(); Number n2 = p2.getValue(); if (n1 instanceof Double || n2 instanceof Double) { @@ -50,10 +51,7 @@ protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { @Override protected void initAllowedTypeBindings() { - allowedTypeBindings.add(new FunctionTypeBinding(IntegerConstant.class, IntegerConstant.class, IntegerConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, IntegerConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, IntegerConstant.class)); + Utils.allowTypeBindingsCommonNumbers(allowedTypeBindings); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Subtract.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Subtract.java index fd9a828ae..387c89c83 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Subtract.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/arithmetic/Subtract.java @@ -27,6 +27,7 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.constant.NumericConstant; import de.fraunhofer.iosb.ilt.sta.query.expression.function.Function; import de.fraunhofer.iosb.ilt.sta.query.expression.function.FunctionTypeBinding; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.Utils; /** * @@ -42,7 +43,7 @@ public Subtract(Expression... parameters) { super(parameters); } - protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { + protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { Number n1 = p1.getValue(); Number n2 = p2.getValue(); if (n1 instanceof Double || n2 instanceof Double) { @@ -54,16 +55,13 @@ protected NumericConstant eval(NumericConstant p1, NumericConstant p2) { @Override protected void initAllowedTypeBindings() { + Utils.allowTypeBindingsCommonNumbers(allowedTypeBindings); + allowedTypeBindings.add(new FunctionTypeBinding(DateTimeConstant.class, DateTimeConstant.class, DurationConstant.class)); allowedTypeBindings.add(new FunctionTypeBinding(DurationConstant.class, DurationConstant.class, DurationConstant.class)); allowedTypeBindings.add(new FunctionTypeBinding(DateTimeConstant.class, DateConstant.class, DurationConstant.class)); allowedTypeBindings.add(new FunctionTypeBinding(DurationConstant.class, DateTimeConstant.class, DateTimeConstant.class)); allowedTypeBindings.add(new FunctionTypeBinding(DurationConstant.class, DateConstant.class, DateConstant.class)); - - allowedTypeBindings.add(new FunctionTypeBinding(IntegerConstant.class, IntegerConstant.class, IntegerConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, IntegerConstant.class, DoubleConstant.class)); - allowedTypeBindings.add(new FunctionTypeBinding(DoubleConstant.class, DoubleConstant.class, IntegerConstant.class)); } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/Equal.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/Equal.java index a136d1d15..2f9b99aee 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/Equal.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/Equal.java @@ -28,11 +28,15 @@ */ public class Equal extends ComparisonFunction { + public Equal() { + // Parameters added later... + } + public Equal(Expression... parameters) { super(parameters); } - protected BooleanConstant eval(Constant p1, Constant p2) { + public BooleanConstant eval(Constant p1, Constant p2) { return new BooleanConstant(p1.equals(p2)); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterEqual.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterEqual.java index 12b41427b..2e8eec099 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterEqual.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterEqual.java @@ -28,19 +28,18 @@ */ public class GreaterEqual extends ComparisonFunction { + public GreaterEqual() { + // Parameters added later... + } + public GreaterEqual(Expression... parameters) { super(parameters); } - protected BooleanConstant eval(NumericConstant p1, NumericConstant p2) { + public BooleanConstant eval(NumericConstant p1, NumericConstant p2) { return new BooleanConstant(p1.getValue().doubleValue() >= p2.getValue().doubleValue()); } - @Override - protected void initAllowedTypeBindings() { - - } - @Override public String toUrl() { return "(" + parameters.get(0).toUrl() + " ge " + parameters.get(1).toUrl() + ")"; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterThan.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterThan.java index 74eb2f2e5..e8166bcbf 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterThan.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/GreaterThan.java @@ -28,11 +28,15 @@ */ public class GreaterThan extends ComparisonFunction { + public GreaterThan() { + // Parameters added later... + } + public GreaterThan(Expression... parameters) { super(parameters); } - protected BooleanConstant eval(NumericConstant p1, NumericConstant p2) { + public BooleanConstant eval(NumericConstant p1, NumericConstant p2) { return new BooleanConstant(p1.getValue().doubleValue() > p2.getValue().doubleValue()); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessEqual.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessEqual.java index da9804ad0..c4e77b700 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessEqual.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessEqual.java @@ -28,19 +28,18 @@ */ public class LessEqual extends ComparisonFunction { + public LessEqual() { + // Parameters added later... + } + public LessEqual(Expression... parameters) { super(parameters); } - protected BooleanConstant eval(NumericConstant p1, NumericConstant p2) { + public BooleanConstant eval(NumericConstant p1, NumericConstant p2) { return new BooleanConstant(p1.getValue().doubleValue() <= p2.getValue().doubleValue()); } - @Override - protected void initAllowedTypeBindings() { - - } - @Override public String toUrl() { return "(" + parameters.get(0).toUrl() + " le " + parameters.get(1).toUrl() + ")"; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessThan.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessThan.java index 57a4422c6..73db4a4e0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessThan.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/LessThan.java @@ -28,19 +28,18 @@ */ public class LessThan extends ComparisonFunction { + public LessThan() { + // Parameters added later... + } + public LessThan(Expression... parameters) { super(parameters); } - protected BooleanConstant eval(NumericConstant p1, NumericConstant p2) { + public BooleanConstant eval(NumericConstant p1, NumericConstant p2) { return new BooleanConstant(p1.getValue().doubleValue() < p2.getValue().doubleValue()); } - @Override - protected void initAllowedTypeBindings() { - - } - @Override public String toUrl() { return "(" + parameters.get(0).toUrl() + " lt " + parameters.get(1).toUrl() + ")"; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/NotEqual.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/NotEqual.java index 2f0fbf473..af8ef99da 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/NotEqual.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/comparison/NotEqual.java @@ -28,12 +28,16 @@ */ public class NotEqual extends Equal { + public NotEqual() { + // Parameters added later... + } + public NotEqual(Expression... parameters) { super(parameters); } @Override - protected BooleanConstant eval(Constant p1, Constant p2) { + public BooleanConstant eval(Constant p1, Constant p2) { return new BooleanConstant(!super.eval(p1, p2).getValue()); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Date.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Date.java index 9549ef54e..9f344685c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Date.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Date.java @@ -30,6 +30,10 @@ */ public class Date extends Function { + public Date() { + // Parameters added later... + } + public Date(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Day.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Day.java index 94e58f295..72be8e37f 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Day.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Day.java @@ -31,6 +31,10 @@ */ public class Day extends Function { + public Day() { + // Parameters added later... + } + public Day(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/FractionalSeconds.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/FractionalSeconds.java index b1aab6348..8b495d90b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/FractionalSeconds.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/FractionalSeconds.java @@ -31,6 +31,10 @@ */ public class FractionalSeconds extends Function { + public FractionalSeconds() { + // Parameters added later... + } + public FractionalSeconds(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Hour.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Hour.java index a5e2d8511..0f84ad54c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Hour.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Hour.java @@ -31,6 +31,10 @@ */ public class Hour extends Function { + public Hour() { + // Parameters added later... + } + public Hour(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MaxDateTime.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MaxDateTime.java index 82225bb7b..6dc9d977b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MaxDateTime.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MaxDateTime.java @@ -30,6 +30,10 @@ */ public class MaxDateTime extends Function { + public MaxDateTime() { + // Parameters added later... + } + public MaxDateTime(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MinDateTime.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MinDateTime.java index 1e47b773d..560f776e1 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MinDateTime.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/MinDateTime.java @@ -30,6 +30,10 @@ */ public class MinDateTime extends Function { + public MinDateTime() { + // Parameters added later... + } + public MinDateTime(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Minute.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Minute.java index d075992a6..a093dd8a0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Minute.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Minute.java @@ -31,6 +31,10 @@ */ public class Minute extends Function { + public Minute() { + // Parameters added later... + } + public Minute(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Month.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Month.java index c2db7e647..1a6bfa145 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Month.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Month.java @@ -31,6 +31,10 @@ */ public class Month extends Function { + public Month() { + // Parameters added later... + } + public Month(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Now.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Now.java index b1c0f4b1d..9d72fbb75 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Now.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Now.java @@ -30,6 +30,10 @@ */ public class Now extends Function { + public Now() { + // Parameters added later... + } + public Now(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Second.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Second.java index 09259d334..af7f543b2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Second.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Second.java @@ -31,6 +31,10 @@ */ public class Second extends Function { + public Second() { + // Parameters added later... + } + public Second(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Time.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Time.java index fa5be64b8..1a6c54ee6 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Time.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Time.java @@ -30,6 +30,10 @@ */ public class Time extends Function { + public Time() { + // Parameters added later... + } + public Time(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/TotalOffsetMinutes.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/TotalOffsetMinutes.java index 74a83ca7c..d5843f2e7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/TotalOffsetMinutes.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/TotalOffsetMinutes.java @@ -30,6 +30,10 @@ */ public class TotalOffsetMinutes extends Function { + public TotalOffsetMinutes() { + // Parameters added later... + } + public TotalOffsetMinutes(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Year.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Year.java index 92d962ee8..8b102c210 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Year.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/date/Year.java @@ -31,6 +31,10 @@ */ public class Year extends Function { + public Year() { + // Parameters added later... + } + public Year(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/And.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/And.java index 7875bc9a0..13d9c4137 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/And.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/And.java @@ -29,6 +29,10 @@ */ public class And extends Function { + public And() { + // Parameters added later... + } + public And(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Not.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Not.java index b6a1b7aba..b972b627b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Not.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Not.java @@ -29,6 +29,10 @@ */ public class Not extends Function { + public Not() { + // Parameters added later... + } + public Not(Expression... parameters) { super(parameters); } @@ -44,7 +48,7 @@ protected void initAllowedTypeBindings() { @Override public String toUrl() { - return "(" + " not (" + parameters.get(0).toUrl() + "))"; + return "( not (" + parameters.get(0).toUrl() + "))"; } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Or.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Or.java index 5a3048f34..f6309489d 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Or.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/logical/Or.java @@ -29,6 +29,10 @@ */ public class Or extends Function { + public Or() { + // Parameters added later... + } + public Or(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Ceiling.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Ceiling.java index 52ba1a3a4..df11a30b0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Ceiling.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Ceiling.java @@ -29,6 +29,10 @@ */ public class Ceiling extends Function { + public Ceiling() { + // Parameters added later... + } + public Ceiling(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Floor.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Floor.java index d2e94cc4f..8da85b40f 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Floor.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Floor.java @@ -29,6 +29,10 @@ */ public class Floor extends Function { + public Floor() { + // Parameters added later... + } + public Floor(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Round.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Round.java index 0e90a1c8a..18d96c99c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Round.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/math/Round.java @@ -29,6 +29,10 @@ */ public class Round extends Function { + public Round() { + // Parameters added later... + } + public Round(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoDistance.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoDistance.java similarity index 92% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoDistance.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoDistance.java index 477e9ccc1..9d8d9ef4e 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoDistance.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoDistance.java @@ -15,7 +15,7 @@ * 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.sta.query.expression.function.geospatial; +package de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation; import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; import de.fraunhofer.iosb.ilt.sta.query.expression.ExpressionVisitor; @@ -30,6 +30,10 @@ */ public class GeoDistance extends Function { + public GeoDistance() { + super("geo.distance"); + } + public GeoDistance(Expression... parameters) { super("geo.distance", parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoIntersects.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoIntersects.java similarity index 92% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoIntersects.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoIntersects.java index 5bb3ac377..98e4b3a49 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoIntersects.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoIntersects.java @@ -15,7 +15,7 @@ * 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.sta.query.expression.function.geospatial; +package de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation; import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; import de.fraunhofer.iosb.ilt.sta.query.expression.ExpressionVisitor; @@ -31,6 +31,10 @@ */ public class GeoIntersects extends Function { + public GeoIntersects() { + super("geo.intersects"); + } + public GeoIntersects(Expression... parameters) { super("geo.intersects", parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoLength.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoLength.java similarity index 92% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoLength.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoLength.java index aae4f0367..6502c514d 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/geospatial/GeoLength.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/GeoLength.java @@ -15,7 +15,7 @@ * 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.sta.query.expression.function.geospatial; +package de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation; import de.fraunhofer.iosb.ilt.sta.query.expression.Expression; import de.fraunhofer.iosb.ilt.sta.query.expression.ExpressionVisitor; @@ -30,6 +30,10 @@ */ public class GeoLength extends Function { + public GeoLength() { + super("geo.length"); + } + public GeoLength(Expression... parameters) { super("geo.length", parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STContains.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STContains.java index 681d3e05b..9a62672ae 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STContains.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STContains.java @@ -27,13 +27,17 @@ */ public class STContains extends Function { + public STContains() { + super("st_contains"); + } + public STContains(Expression... parameters) { super("st_contains", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STCrosses.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STCrosses.java index c08c146cd..06033217c 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STCrosses.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STCrosses.java @@ -27,13 +27,17 @@ */ public class STCrosses extends Function { + public STCrosses() { + super("st_crosses"); + } + public STCrosses(Expression... parameters) { super("st_crosses", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STDisjoint.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STDisjoint.java index e93acfbf4..17a3e487a 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STDisjoint.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STDisjoint.java @@ -27,13 +27,17 @@ */ public class STDisjoint extends Function { + public STDisjoint() { + super("st_disjoint"); + } + public STDisjoint(Expression... parameters) { super("st_disjoint", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STEquals.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STEquals.java index 93cfaf99b..60eb7d573 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STEquals.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STEquals.java @@ -27,13 +27,17 @@ */ public class STEquals extends Function { + public STEquals() { + super("st_equals"); + } + public STEquals(Expression... parameters) { super("st_equals", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STIntersects.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STIntersects.java index 48235a471..791362ae3 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STIntersects.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STIntersects.java @@ -27,13 +27,17 @@ */ public class STIntersects extends Function { + public STIntersects() { + super("st_intersects"); + } + public STIntersects(Expression... parameters) { super("st_intersects", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STOverlaps.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STOverlaps.java index bb7937ed4..a115f15f7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STOverlaps.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STOverlaps.java @@ -27,13 +27,17 @@ */ public class STOverlaps extends Function { + public STOverlaps() { + super("st_overlaps"); + } + public STOverlaps(Expression... parameters) { super("st_overlaps", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STRelate.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STRelate.java index d2edc403f..1e81cfa54 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STRelate.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STRelate.java @@ -27,13 +27,17 @@ */ public class STRelate extends Function { + public STRelate() { + super("st_relate"); + } + public STRelate(Expression... parameters) { super("st_relate", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STTouches.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STTouches.java index da47d477e..8c05cbcbc 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STTouches.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STTouches.java @@ -27,13 +27,17 @@ */ public class STTouches extends Function { + public STTouches() { + super("st_touches"); + } + public STTouches(Expression... parameters) { super("st_touches", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STWithin.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STWithin.java index 96043dbd8..e32934799 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STWithin.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/spatialrelation/STWithin.java @@ -27,13 +27,17 @@ */ public class STWithin extends Function { + public STWithin() { + super("st_within"); + } + public STWithin(Expression... parameters) { super("st_within", parameters); } @Override protected void initAllowedTypeBindings() { - + // No default bindings. } @Override diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Concat.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Concat.java index 3170580b3..814f52f21 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Concat.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Concat.java @@ -29,6 +29,10 @@ */ public class Concat extends Function { + public Concat() { + // Parameters added later... + } + public Concat(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/EndsWith.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/EndsWith.java index 4ff5be4ac..677b52771 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/EndsWith.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/EndsWith.java @@ -30,6 +30,10 @@ */ public class EndsWith extends Function { + public EndsWith() { + // Parameters added later... + } + public EndsWith(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/IndexOf.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/IndexOf.java index fe7601a65..3f74a3aa0 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/IndexOf.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/IndexOf.java @@ -30,6 +30,10 @@ */ public class IndexOf extends Function { + public IndexOf() { + // Parameters added later... + } + public IndexOf(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Length.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Length.java index 6ed00514c..f83527153 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Length.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Length.java @@ -30,6 +30,10 @@ */ public class Length extends Function { + public Length() { + // Parameters added later... + } + public Length(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/StartsWith.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/StartsWith.java index a0895d4ac..683882b9a 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/StartsWith.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/StartsWith.java @@ -30,6 +30,10 @@ */ public class StartsWith extends Function { + public StartsWith() { + // Parameters added later... + } + public StartsWith(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Substring.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Substring.java index ad8f3b1f5..dbcbf5c28 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Substring.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Substring.java @@ -30,6 +30,10 @@ */ public class Substring extends Function { + public Substring() { + // Parameters added later... + } + public Substring(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/SubstringOf.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/SubstringOf.java index 7b87cd5a6..dc185c741 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/SubstringOf.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/SubstringOf.java @@ -30,6 +30,10 @@ */ public class SubstringOf extends Function { + public SubstringOf() { + // Parameters added later... + } + public SubstringOf(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToLower.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToLower.java index 664dbb1ff..89d7a4b66 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToLower.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToLower.java @@ -29,6 +29,10 @@ */ public class ToLower extends Function { + public ToLower() { + // Parameters added later... + } + public ToLower(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToUpper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToUpper.java index fa4436f29..3f5d22f23 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToUpper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/ToUpper.java @@ -29,6 +29,10 @@ */ public class ToUpper extends Function { + public ToUpper() { + // Parameters added later... + } + public ToUpper(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Trim.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Trim.java index b4daead09..14e1848b6 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Trim.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/string/Trim.java @@ -29,6 +29,10 @@ */ public class Trim extends Function { + public Trim() { + // Parameters added later... + } + public Trim(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/After.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/After.java index 9d8597f88..a376a47c9 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/After.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/After.java @@ -31,6 +31,10 @@ */ public class After extends Function { + public After() { + // Parameters added later... + } + public After(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Before.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Before.java index f56053db7..29574602d 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Before.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Before.java @@ -31,6 +31,10 @@ */ public class Before extends Function { + public Before() { + // Parameters added later... + } + public Before(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/During.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/During.java index 10ebfdf1f..717da42ad 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/During.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/During.java @@ -31,6 +31,10 @@ */ public class During extends Function { + public During() { + // Parameters added later... + } + public During(Expression... parameters) { super(parameters); } @@ -39,12 +43,10 @@ protected BooleanConstant eval(IntervalConstant p1, IntervalConstant p2) { return new BooleanConstant(p2.getValue().contains(p1.getValue())); } - protected BooleanConstant eval(DateTimeConstant p1, IntervalConstant p2) { return new BooleanConstant(p2.getValue().contains(p1.getValue())); } - @Override protected void initAllowedTypeBindings() { allowedTypeBindings.add(new FunctionTypeBinding(BooleanConstant.class, IntervalConstant.class, IntervalConstant.class)); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Finishes.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Finishes.java index cd1dfdf66..69cc88dab 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Finishes.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Finishes.java @@ -31,6 +31,10 @@ */ public class Finishes extends Function { + public Finishes() { + // Parameters added later... + } + public Finishes(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Meets.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Meets.java index 6ff2607f3..116f34516 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Meets.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Meets.java @@ -31,6 +31,10 @@ */ public class Meets extends Function { + public Meets() { + // Parameters added later... + } + public Meets(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Overlaps.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Overlaps.java index f36ef6e98..8d45b98e3 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Overlaps.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Overlaps.java @@ -31,6 +31,10 @@ */ public class Overlaps extends Function { + public Overlaps() { + // Parameters added later... + } + public Overlaps(Expression... parameters) { super(parameters); } @@ -39,12 +43,10 @@ protected BooleanConstant eval(IntervalConstant p1, IntervalConstant p2) { return new BooleanConstant(p2.getValue().overlaps(p1.getValue())); } - protected BooleanConstant eval(DateTimeConstant p1, IntervalConstant p2) { return new BooleanConstant(p2.getValue().contains(p1.getValue())); } - @Override protected void initAllowedTypeBindings() { allowedTypeBindings.add(new FunctionTypeBinding(BooleanConstant.class, IntervalConstant.class, IntervalConstant.class)); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Starts.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Starts.java index 20d6c639c..dbf1ba9e2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Starts.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/query/expression/function/temporal/Starts.java @@ -31,6 +31,10 @@ */ public class Starts extends Function { + public Starts() { + // Parameters added later... + } + public Starts(Expression... parameters) { super(parameters); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntityFormatter.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntityFormatter.java deleted file mode 100644 index 297f1951a..000000000 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/serialize/EntityFormatter.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.serialize; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; -import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayResult; -import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayValue; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.custom.geojson.GeoJsonSerializer; -import de.fraunhofer.iosb.ilt.sta.model.ext.EntitySetResult; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.mixin.DatastreamMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.EntitySetResultMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.FeatureOfInterestMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.HistoricalLocationMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.LocationMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.MultiDatastreamMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.ObservationMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.ObservedPropertyMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.SensorMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.ThingMixIn; -import de.fraunhofer.iosb.ilt.sta.model.mixin.UnitOfMeasurementMixIn; -import de.fraunhofer.iosb.ilt.sta.path.Property; -import de.fraunhofer.iosb.ilt.sta.serialize.custom.CustomSerializationManager; -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Enables serialization of entities as JSON. - * - * @author jab - */ -public class EntityFormatter { - - private final ObjectMapper mapper; - - public EntityFormatter() { - this(null); - } - - public EntityFormatter(List selectedProperties) { - mapper = new ObjectMapper(); - mapper.enable(SerializationFeature.INDENT_OUTPUT); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - mapper.setPropertyNamingStrategy(new EntitySetCamelCaseNamingStrategy()); - mapper.addMixIn(Datastream.class, DatastreamMixIn.class); - mapper.addMixIn(MultiDatastream.class, MultiDatastreamMixIn.class); - mapper.addMixIn(FeatureOfInterest.class, FeatureOfInterestMixIn.class); - mapper.addMixIn(HistoricalLocation.class, HistoricalLocationMixIn.class); - mapper.addMixIn(Location.class, LocationMixIn.class); - mapper.addMixIn(Observation.class, ObservationMixIn.class); - mapper.addMixIn(ObservedProperty.class, ObservedPropertyMixIn.class); - mapper.addMixIn(Sensor.class, SensorMixIn.class); - mapper.addMixIn(Thing.class, ThingMixIn.class); - mapper.addMixIn(UnitOfMeasurement.class, UnitOfMeasurementMixIn.class); - mapper.addMixIn(EntitySetResult.class, EntitySetResultMixIn.class); - - SimpleModule module = new SimpleModule(); - GeoJsonSerializer geoJsonSerializer = new GeoJsonSerializer(); - for (String encodingType : GeoJsonSerializer.encodings) { - CustomSerializationManager.getInstance().registerSerializer(encodingType, geoJsonSerializer); - } - - if (selectedProperties != null && !selectedProperties.isEmpty()) { - module.addSerializer(Entity.class, new EntitySerializer(selectedProperties.stream().map(x -> x.getName()).collect(Collectors.toList()))); - } else { - module.addSerializer(Entity.class, new EntitySerializer()); - } - module.addSerializer(EntitySetResult.class, new EntitySetResultSerializer()); - module.addSerializer(DataArrayValue.class, new DataArrayValueSerializer()); - module.addSerializer(DataArrayResult.class, new DataArrayResultSerializer()); - mapper.registerModule(module); - } - - public String writeEntity(T entity) throws IOException { - return mapper.writeValueAsString(entity); - } - - public String writeEntityCollection(EntitySet entityCollection) throws IOException { - return mapper.writeValueAsString(new EntitySetResult(entityCollection)); - } - - public String writeDatastream(Datastream datastream) throws IOException { - return writeEntity(datastream); - } - - public String writeFeatureOfInterest(FeatureOfInterest featureOfInterest) throws IOException { - return writeEntity(featureOfInterest); - } - - public String writeHistoricalLocation(HistoricalLocation historicalLocation) throws IOException { - return writeEntity(historicalLocation); - } - - public String writeLocation(Location location) throws IOException { - return writeEntity(location); - } - - public String writeObservation(Observation observation) throws IOException { - return writeEntity(observation); - } - - public String writeObservedProperty(ObservedProperty observedProperty) throws IOException { - return writeEntity(observedProperty); - } - - public String writeSensor(Sensor sensor) throws IOException { - return writeEntity(sensor); - } - - public String writeThing(Thing thing) throws IOException { - return writeEntity(thing); - } - - public String writeObject(Object object) throws IOException { - return mapper.writeValueAsString(object); - } -} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/RequestType.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/RequestType.java index f70af4eb3..4116ef9b7 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/RequestType.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/RequestType.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.service; @@ -21,11 +22,11 @@ * @author jab */ public enum RequestType { - GetCapabilities, - Create, - CreateObservations, - Read, - UpdateAll, - UpdateChanges, - Delete + GET_CAPABILITIES, + CREATE, + CREATE_OBSERVATIONS, + READ, + UPDATE_ALL, + UPDATE_CHANGES, + DELETE } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/Service.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/Service.java index 47e2646cc..8a39bc582 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/Service.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/Service.java @@ -1,26 +1,28 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.service; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; -import de.fraunhofer.iosb.ilt.sta.deserialize.EntityParser; import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayValue; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; import de.fraunhofer.iosb.ilt.sta.model.Observation; import de.fraunhofer.iosb.ilt.sta.model.builder.ObservationBuilder; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; @@ -29,7 +31,9 @@ import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; import de.fraunhofer.iosb.ilt.sta.query.Query; @@ -72,6 +76,10 @@ public class Service { private static final Logger LOGGER = LoggerFactory.getLogger(Service.class); + private static final String NOT_A_VALID_ID = "Not a valid id"; + private static final String POST_ONLY_ALLOWED_TO_COLLECTIONS = "POST only allowed to Collections."; + private static final String COULD_NOT_PARSE_JSON = "Could not parse json."; + private final CoreSettings settings; private PersistenceManager persistenceManager; private boolean transactionActive = false; @@ -83,19 +91,19 @@ public Service(CoreSettings settings) { public ServiceResponse execute(ServiceRequest request) { switch (request.getRequestType()) { - case GetCapabilities: + case GET_CAPABILITIES: return executeGetCapabilities(request); - case Create: + case CREATE: return executePost(request); - case CreateObservations: + case CREATE_OBSERVATIONS: return executeCreateObservations(request); - case Read: + case READ: return executeGet(request); - case Delete: + case DELETE: return executeDelete(request); - case UpdateAll: + case UPDATE_ALL: return executePut(request); - case UpdateChanges: + case UPDATE_CHANGES: return executePatch(request); default: return new ServiceResponse<>(500, "Illegal request type."); @@ -204,58 +212,7 @@ private ServiceResponse executeGet(ServiceRequest request) { ServiceResponse response = new ServiceResponse<>(); PersistenceManager pm = getPm(); try { - ResourcePath path; - try { - path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), request.getUrlPath()); - } catch (IllegalArgumentException e) { - return response.setStatus(404, "Not a valid id."); - } catch (IllegalStateException e) { - return response.setStatus(404, "Not a valid id: " + e.getMessage()); - } - Query query; - try { - query = QueryParser.parseQuery(request.getUrlQuery(), settings); - } catch (IllegalArgumentException e) { - return response.setStatus(404, "Invalid query: " + e.getMessage()); - } - try { - query.validate(path); - } catch (IllegalArgumentException ex) { - return response.setStatus(400, ex.getMessage()); - } - if (!pm.validatePath(path)) { - response.setStatus(404, "Nothing found."); - maybeCommitAndClose(); - return response; - } - T object; - try { - object = (T) pm.get(path, query); - } catch (UnsupportedOperationException e) { - LOGGER.error("Unsupported operation.", e); - response.setStatus(500, "Unsupported operation: " + e.getMessage()); - pm.rollbackAndClose(); - return response; - } catch (IllegalArgumentException e) { - LOGGER.debug("Illegal operation.", e); - response.setStatus(400, "Illegal operation: " + e.getMessage()); - pm.rollbackAndClose(); - return response; - } catch (ClassCastException e) { - LOGGER.error("Result did not match expected format", e); - response.setStatus(500, "Illegal result type: " + e.getMessage()); - pm.rollbackAndClose(); - return response; - } - if (object == null) { - response.setStatus(404, "Nothing found."); - maybeCommitAndClose(); - return response; - } - response.setResult(object); - response.setResultFormatted(request.getFormatter().format(path, query, object, settings.isUseAbsoluteNavigationLinks())); - response.setCode(200); - maybeCommitAndClose(); + return handleGet(pm, request, response); } catch (Exception e) { response.setStatus(500, "Failed to execute query. See logs for details."); LOGGER.error("", e); @@ -268,63 +225,88 @@ private ServiceResponse executeGet(ServiceRequest request) { return response; } + private ServiceResponse handleGet(PersistenceManager pm, ServiceRequest request, ServiceResponse response) { + ResourcePath path; + try { + path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), request.getUrlPath()); + } catch (IllegalArgumentException e) { + response.setStatus(404, NOT_A_VALID_ID); + return response; + } catch (IllegalStateException e) { + response.setStatus(404, NOT_A_VALID_ID + ": " + e.getMessage()); + return response; + } + Query query; + try { + query = QueryParser.parseQuery(request.getUrlQuery(), settings); + } catch (IllegalArgumentException e) { + response.setStatus(404, "Failed to parse query: " + e.getMessage()); + return response; + } + + // If DataArray is requested, and $select is used, make sure Datastream is in the $select. + if ("dataarray".equalsIgnoreCase(query.getFormat()) && !query.getSelect().isEmpty()) { + ResourcePathElement lastElement = path.getLastElement(); + if (lastElement instanceof EntitySetPathElement && ((EntitySetPathElement) lastElement).getEntityType() == EntityType.OBSERVATION) { + query.getSelect().add(NavigationProperty.DATASTREAM); + } + } + + try { + query.validate(path); + } catch (IllegalArgumentException ex) { + response.setStatus(400, ex.getMessage()); + return response; + } + if (!pm.validatePath(path)) { + response.setStatus(404, "Nothing found."); + maybeCommitAndClose(); + return response; + } + T object; + try { + object = (T) pm.get(path, query); + } catch (UnsupportedOperationException e) { + LOGGER.error("Unsupported operation.", e); + response.setStatus(500, "Unsupported operation: " + e.getMessage()); + pm.rollbackAndClose(); + return response; + } catch (IllegalArgumentException e) { + LOGGER.debug("Illegal operation.", e); + response.setStatus(400, "Illegal operation: " + e.getMessage()); + pm.rollbackAndClose(); + return response; + } catch (ClassCastException e) { + LOGGER.error("Result did not match expected format", e); + response.setStatus(500, "Illegal result type: " + e.getMessage()); + pm.rollbackAndClose(); + return response; + } + if (object == null) { + if (path.isValue() || path.isEntityProperty()) { + response.setStatus(204, "No Content"); + } else { + response.setStatus(404, "Nothing found."); + } + } else { + response.setResult(object); + response.setResultFormatted(request.getFormatter().format(path, query, object, settings.isUseAbsoluteNavigationLinks())); + response.setCode(200); + } + maybeCommitAndClose(); + return response; + } + private ServiceResponse executePost(ServiceRequest request) { ServiceResponse response = new ServiceResponse<>(); String urlPath = request.getUrlPath(); if (urlPath == null || urlPath.equals("/")) { - return response.setStatus(400, "POST only allowed to Collections."); + return response.setStatus(400, POST_ONLY_ALLOWED_TO_COLLECTIONS); } PersistenceManager pm = getPm(); try { - ResourcePath path; - try { - path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), urlPath); - } catch (IllegalArgumentException e) { - return response.setStatus(404, "Not a valid id."); - } catch (IllegalStateException e) { - return response.setStatus(404, "Not a valid id: " + e.getMessage()); - } - if (!(path.getMainElement() instanceof EntitySetPathElement)) { - return response.setStatus(400, "POST only allowed to Collections."); - } - if (request.getUrlQuery() != null && !request.getUrlQuery().isEmpty()) { - return response.setStatus(400, "Not query options allowed on POST."); - } - - EntitySetPathElement mainSet = (EntitySetPathElement) path.getMainElement(); - EntityType type = mainSet.getEntityType(); - EntityParser entityParser = new EntityParser(pm.getIdManager().getIdClass()); - Entity entity; - try { - entity = entityParser.parseEntity(type.getImplementingClass(), request.getContent()); - entity.complete(mainSet); - } catch (JsonParseException | JsonMappingException | IncompleteEntityException | IllegalStateException ex) { - LOGGER.debug("Post failed.", ex.getMessage()); - LOGGER.debug("Exception:", ex); - return response.setStatus(400, ex.getMessage()); - } - - try { - if (pm.insert(entity)) { - maybeCommitAndClose(); - String url = UrlHelper.generateSelfLink(path, entity); - try { - response.setResult((T) entity); - } catch (ClassCastException ex) { - LOGGER.debug("Could not cas result to desired format", ex); - } - response.setCode(201); - response.addHeader("location", url); - } else { - LOGGER.debug("Failed to insert entity."); - pm.rollbackAndClose(); - return response.setStatus(400, "Failed to insert entity."); - } - } catch (IllegalArgumentException | IncompleteEntityException | NoSuchEntityException e) { - pm.rollbackAndClose(); - return response.setStatus(400, e.getMessage()); - } + return handlePost(pm, urlPath, response, request); } catch (Exception e) { LOGGER.error("", e); if (pm != null) { @@ -334,6 +316,52 @@ private ServiceResponse executePost(ServiceRequest request) { } finally { maybeRollbackAndClose(); } + } + + private ServiceResponse handlePost(PersistenceManager pm, String urlPath, ServiceResponse response, ServiceRequest request) throws IOException { + ResourcePath path; + try { + path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), urlPath); + } catch (IllegalArgumentException e) { + return response.setStatus(404, NOT_A_VALID_ID); + } catch (IllegalStateException e) { + return response.setStatus(404, NOT_A_VALID_ID + ": " + e.getMessage()); + } + if (!(path.getMainElement() instanceof EntitySetPathElement)) { + return response.setStatus(400, POST_ONLY_ALLOWED_TO_COLLECTIONS); + } + if (request.getUrlQuery() != null && !request.getUrlQuery().isEmpty()) { + return response.setStatus(400, "Not query options allowed on POST."); + } + + EntitySetPathElement mainSet = (EntitySetPathElement) path.getMainElement(); + EntityType type = mainSet.getEntityType(); + EntityParser entityParser = new EntityParser(pm.getIdManager().getIdClass()); + Entity entity; + try { + entity = entityParser.parseEntity(type.getImplementingClass(), request.getContent()); + entity.complete(mainSet); + } catch (JsonParseException | JsonMappingException | IncompleteEntityException | IllegalStateException ex) { + LOGGER.debug("Post failed: {}", ex.getMessage()); + LOGGER.trace("Exception:", ex); + return response.setStatus(400, ex.getMessage()); + } + + try { + if (!pm.insert(entity)) { + LOGGER.debug("Failed to insert entity."); + pm.rollbackAndClose(); + return response.setStatus(400, "Failed to insert entity."); + } + maybeCommitAndClose(); + String url = UrlHelper.generateSelfLink(path, entity); + response.setResult((T) entity); + response.setCode(201); + response.addHeader("location", url); + } catch (IllegalArgumentException | IncompleteEntityException | NoSuchEntityException e) { + pm.rollbackAndClose(); + return response.setStatus(400, e.getMessage()); + } return response; } @@ -341,7 +369,7 @@ private ServiceResponse executeCreateObservations(ServiceRequest request) ServiceResponse response = new ServiceResponse<>(); String urlPath = request.getUrlPath(); if (!("/CreateObservations".equals(urlPath))) { - return response.setStatus(400, "POST only allowed to Collections."); + return response.setStatus(400, POST_ONLY_ALLOWED_TO_COLLECTIONS); } PersistenceManager pm = getPm(); @@ -351,37 +379,43 @@ private ServiceResponse executeCreateObservations(ServiceRequest request) List selfLinks = new ArrayList<>(); for (DataArrayValue daValue : postData) { Datastream datastream = daValue.getDatastream(); + MultiDatastream multiDatastream = daValue.getMultiDatastream(); List handlers = new ArrayList<>(); for (String component : daValue.getComponents()) { handlers.add(ArrayValueHandlers.getHandler(component)); } - int compCount = handlers.size(); - for (List entry : daValue.getDataArray()) { - try { - ObservationBuilder obsBuilder = new ObservationBuilder(); - obsBuilder.setDatastream(datastream); - for (int i = 0; i < compCount; i++) { - handlers.get(i).handle(entry.get(i), obsBuilder); - } - Observation observation = obsBuilder.build(); - pm.insert(observation); - String selfLink = UrlHelper.generateSelfLink(settings.getServiceRootUrl(), observation); - selfLinks.add(selfLink); - } catch (NoSuchEntityException | IncompleteEntityException | IllegalArgumentException e) { - LOGGER.debug("Failed to create entity", e); - selfLinks.add("error " + e.getMessage()); - } - } + handleDataArrayItems(handlers, daValue, datastream, multiDatastream, pm, selfLinks); } maybeCommitAndClose(); response.setResultFormatted(request.getFormatter().format(null, null, selfLinks, settings.isUseAbsoluteNavigationLinks())); return response.setStatus(201, "Created"); - } catch (IllegalArgumentException | IncompleteEntityException | IOException e) { + } catch (IllegalArgumentException | IOException e) { pm.rollbackAndClose(); return response.setStatus(400, e.getMessage()); } } + private void handleDataArrayItems(List handlers, DataArrayValue daValue, Datastream datastream, MultiDatastream multiDatastream, PersistenceManager pm, List selfLinks) { + int compCount = handlers.size(); + for (List entry : daValue.getDataArray()) { + try { + ObservationBuilder obsBuilder = new ObservationBuilder(); + obsBuilder.setDatastream(datastream); + obsBuilder.setMultiDatastream(multiDatastream); + for (int i = 0; i < compCount; i++) { + handlers.get(i).handle(entry.get(i), obsBuilder); + } + Observation observation = obsBuilder.build(); + pm.insert(observation); + String selfLink = UrlHelper.generateSelfLink(settings.getServiceRootUrl(), observation); + selfLinks.add(selfLink); + } catch (NoSuchEntityException | IncompleteEntityException | IllegalArgumentException exc) { + LOGGER.debug("Failed to create entity", exc); + selfLinks.add("error " + exc.getMessage()); + } + } + } + private ServiceResponse executePatch(ServiceRequest request) { ServiceResponse response = new ServiceResponse<>(); PersistenceManager pm = null; @@ -391,48 +425,7 @@ private ServiceResponse executePatch(ServiceRequest request) { } pm = getPm(); - ResourcePath path; - try { - path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), request.getUrlPath()); - } catch (IllegalArgumentException exc) { - return response.setStatus(404, "Not a valid id."); - } catch (IllegalStateException e) { - return response.setStatus(404, "Not a valid id: " + e.getMessage()); - } - if (!(path.getMainElement() instanceof EntityPathElement) || path.getMainElement() != path.getLastElement()) { - return response.setStatus(400, "PATCH only allowed on Entities."); - } - EntityPathElement mainEntity = (EntityPathElement) path.getMainElement(); - if (mainEntity.getId() == null) { - return response.setStatus(400, "PATCH only allowed on Entities."); - } - if (request.getUrlQuery() != null && !request.getUrlQuery().isEmpty()) { - return response.setStatus(400, "Not query options allowed on PACTH."); - } - - EntityParser entityParser = new EntityParser(pm.getIdManager().getIdClass()); - EntityType type = mainEntity.getEntityType(); - Entity entity; - try { - entity = entityParser.parseEntity(type.getImplementingClass(), request.getContent()); - } catch (JsonParseException | IncompleteEntityException e) { - LOGGER.debug("Could not parse json.", e); - return response.setStatus(400, "Could not parse json."); - } - - try { - - if (pm.update(mainEntity, entity)) { - maybeCommitAndClose(); - response.setCode(200); - } else { - LOGGER.debug("Failed to update entity."); - pm.rollbackAndClose(); - } - } catch (IllegalArgumentException | NoSuchEntityException e) { - pm.rollbackAndClose(); - response.setStatus(400, e.getMessage()); - } + return handlePatch(pm, request, response); } catch (Exception e) { LOGGER.error("", e); if (pm != null) { @@ -444,6 +437,64 @@ private ServiceResponse executePatch(ServiceRequest request) { return response; } + private ServiceResponse handlePatch(PersistenceManager pm, ServiceRequest request, ServiceResponse response) throws IOException, IncompleteEntityException { + EntityPathElement mainElement; + Entity entity; + try { + mainElement = parsePathForPutPatch(pm, request, response); + EntityParser entityParser = new EntityParser(pm.getIdManager().getIdClass()); + entity = entityParser.parseEntity(mainElement.getEntityType().getImplementingClass(), request.getContent()); + } catch (IllegalArgumentException exc) { + LOGGER.trace("Path not valid.", exc); + return response; + } catch (JsonParseException exc) { + LOGGER.debug(COULD_NOT_PARSE_JSON, exc); + return response.setStatus(400, COULD_NOT_PARSE_JSON); + } + + try { + if (pm.update(mainElement, entity)) { + maybeCommitAndClose(); + response.setCode(200); + } else { + LOGGER.debug("Failed to update entity."); + pm.rollbackAndClose(); + } + } catch (IllegalArgumentException | NoSuchEntityException e) { + pm.rollbackAndClose(); + response.setStatus(400, e.getMessage()); + } + return response; + } + + private EntityPathElement parsePathForPutPatch(PersistenceManager pm, ServiceRequest request, ServiceResponse response) { + ResourcePath path; + try { + path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), request.getUrlPath()); + } catch (IllegalArgumentException exc) { + LOGGER.trace(NOT_A_VALID_ID, exc); + response.setStatus(404, NOT_A_VALID_ID); + throw new IllegalArgumentException(NOT_A_VALID_ID); + } catch (IllegalStateException exc) { + response.setStatus(404, NOT_A_VALID_ID + ": " + exc.getMessage()); + throw new IllegalArgumentException(NOT_A_VALID_ID); + } + if (!(path.getMainElement() instanceof EntityPathElement) || path.getMainElement() != path.getLastElement()) { + response.setStatus(400, "PATCH & PUT only allowed on Entities."); + throw new IllegalArgumentException(NOT_A_VALID_ID); + } + EntityPathElement mainElement = (EntityPathElement) path.getMainElement(); + if (mainElement.getId() == null) { + response.setStatus(400, "PATCH & PUT only allowed on Entities."); + throw new IllegalArgumentException(NOT_A_VALID_ID); + } + if (request.getUrlQuery() != null && !request.getUrlQuery().isEmpty()) { + response.setStatus(400, "Not query options allowed on PATCH & PUT."); + throw new IllegalArgumentException(NOT_A_VALID_ID); + } + return mainElement; + } + private ServiceResponse executePut(ServiceRequest request) { ServiceResponse response = new ServiceResponse<>(); PersistenceManager pm = null; @@ -453,50 +504,91 @@ private ServiceResponse executePut(ServiceRequest request) { } pm = getPm(); - ResourcePath path; - try { - path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), request.getUrlPath()); - } catch (IllegalArgumentException e) { - return response.setStatus(404, "Not a valid id."); - } catch (IllegalStateException e) { - return response.setStatus(404, "Not a valid id: " + e.getMessage()); + return handlePut(pm, request, response); + } catch (Exception e) { + LOGGER.error("", e); + if (pm != null) { + pm.rollbackAndClose(); } - if (!(path.getMainElement() instanceof EntityPathElement) || path.getMainElement() != path.getLastElement()) { - return response.setStatus(400, "PATCH only allowed on Entities."); + } finally { + maybeRollbackAndClose(); + } + return response; + } + + private ServiceResponse handlePut(PersistenceManager pm, ServiceRequest request, ServiceResponse response) throws IOException, IncompleteEntityException { + EntityPathElement mainElement; + Entity entity; + try { + mainElement = parsePathForPutPatch(pm, request, response); + + EntityParser entityParser = new EntityParser(pm.getIdManager().getIdClass()); + entity = entityParser.parseEntity(mainElement.getEntityType().getImplementingClass(), request.getContent()); + entity.complete(true); + entity.setEntityPropertiesSet(); + } catch (IllegalArgumentException exc) { + LOGGER.trace("Path not valid.", exc); + return response; + } catch (JsonParseException | IncompleteEntityException e) { + LOGGER.error(COULD_NOT_PARSE_JSON, e); + return response.setStatus(400, COULD_NOT_PARSE_JSON); + } + + try { + if (pm.update(mainElement, entity)) { + maybeCommitAndClose(); + response.setCode(200); + } else { + LOGGER.debug("Failed to update entity."); + pm.rollbackAndClose(); } + } catch (NoSuchEntityException e) { + pm.rollbackAndClose(); + response.setStatus(400, e.getMessage()); + } + return response; + } + + private ServiceResponse executeDelete(ServiceRequest request) { + if (request.getUrlPath() == null || request.getUrlPath().equals("/")) { + return new ServiceResponse<>().setStatus(400, "DELETE only allowed on Entities and Sets."); + } + + ResourcePath path; + try { + path = PathParser.parsePath(getPm().getIdManager(), settings.getServiceRootUrl(), request.getUrlPath()); + } catch (IllegalArgumentException e) { + return new ServiceResponse<>().setStatus(404, NOT_A_VALID_ID); + } catch (IllegalStateException e) { + return new ServiceResponse<>().setStatus(404, NOT_A_VALID_ID + ": " + e.getMessage()); + } + + if ((path.getMainElement() instanceof EntityPathElement)) { + return executeDeleteEntity(request, path); + } + if ((path.getMainElement() instanceof EntitySetPathElement)) { + return executeDeleteEntitySet(request, path); + } + return new ServiceResponse<>().setStatus(400, "Not a valid path for DELETE."); + } + + private ServiceResponse executeDeleteEntity(ServiceRequest request, ResourcePath path) { + ServiceResponse response = new ServiceResponse<>(); + PersistenceManager pm = null; + try { EntityPathElement mainEntity = (EntityPathElement) path.getMainElement(); + if (mainEntity != path.getLastElement()) { + return response.setStatus(400, "DELETE not allowed on properties."); + } if (mainEntity.getId() == null) { - return response.setStatus(400, "PATCH only allowed on Entities."); + return response.setStatus(400, "No ID found."); } if (request.getUrlQuery() != null && !request.getUrlQuery().isEmpty()) { - return response.setStatus(400, "Not query options allowed on PACTH."); - } - - EntityParser entityParser = new EntityParser(pm.getIdManager().getIdClass()); - EntityType type = mainEntity.getEntityType(); - Entity entity; - try { - entity = entityParser.parseEntity(type.getImplementingClass(), request.getContent()); - entity.complete(true); - entity.setEntityPropertiesSet(); - } catch (JsonParseException | IncompleteEntityException e) { - LOGGER.error("Could not parse json.", e); - return response.setStatus(400, "Could not parse json."); + return response.setStatus(400, "No query options allowed on PATH when deleting an entity."); } - try { - - if (pm.update(mainEntity, entity)) { - maybeCommitAndClose(); - response.setCode(200); - } else { - LOGGER.debug("Failed to update entity."); - pm.rollbackAndClose(); - } - } catch (NoSuchEntityException e) { - pm.rollbackAndClose(); - response.setStatus(400, e.getMessage()); - } + pm = getPm(); + return handleDelete(pm, mainEntity, response); } catch (Exception e) { LOGGER.error("", e); if (pm != null) { @@ -508,50 +600,34 @@ private ServiceResponse executePut(ServiceRequest request) { return response; } - private ServiceResponse executeDelete(ServiceRequest request) { + private ServiceResponse handleDelete(PersistenceManager pm, EntityPathElement mainEntity, ServiceResponse response) { + try { + if (pm.delete(mainEntity)) { + maybeCommitAndClose(); + response.setCode(200); + } else { + LOGGER.debug("Failed to delete entity."); + pm.rollbackAndClose(); + } + } catch (NoSuchEntityException e) { + pm.rollbackAndClose(); + response.setStatus(404, e.getMessage()); + } + return response; + } + + private ServiceResponse executeDeleteEntitySet(ServiceRequest request, ResourcePath path) { ServiceResponse response = new ServiceResponse<>(); PersistenceManager pm = null; try { - if (request.getUrlPath() == null || request.getUrlPath().equals("/")) { - return response.setStatus(400, "DELETE only allowed on Entities."); + EntitySetPathElement mainEntity = (EntitySetPathElement) path.getMainElement(); + if (mainEntity != path.getLastElement()) { + return response.setStatus(400, "DELETE not allowed on properties."); } pm = getPm(); - ResourcePath path; - try { - path = PathParser.parsePath(pm.getIdManager(), settings.getServiceRootUrl(), request.getUrlPath()); - } catch (IllegalArgumentException e) { - return response.setStatus(404, "Not a valid id."); - } catch (IllegalStateException e) { - return response.setStatus(404, "Not a valid id: " + e.getMessage()); - } - if (!(path.getMainElement() instanceof EntityPathElement)) { - return response.setStatus(400, "DELETE only allowed on Entities."); - } - if (path.getMainElement() != path.getLastElement()) { - return response.setStatus(400, "DELETE only allowed on Entities."); - } - EntityPathElement mainEntity = (EntityPathElement) path.getMainElement(); - if (mainEntity.getId() == null) { - return response.setStatus(400, "DELETE only allowed on Entities."); - } - if (request.getUrlQuery() != null && !request.getUrlQuery().isEmpty()) { - return response.setStatus(400, "Not query options allowed on PACTH."); - } - - try { - if (pm.delete(mainEntity)) { - maybeCommitAndClose(); - response.setCode(200); - } else { - LOGGER.debug("Failed to delete entity."); - pm.rollbackAndClose(); - } - } catch (NoSuchEntityException e) { - pm.rollbackAndClose(); - response.setStatus(404, e.getMessage()); - } + return handleDeleteSet(request, response, pm, path); } catch (Exception e) { LOGGER.error("", e); if (pm != null) { @@ -562,4 +638,38 @@ private ServiceResponse executeDelete(ServiceRequest request) { } return response; } + + private ServiceResponse handleDeleteSet(ServiceRequest request, ServiceResponse response, PersistenceManager pm, ResourcePath path) { + Query query; + try { + query = QueryParser.parseQuery(request.getUrlQuery(), settings); + } catch (IllegalArgumentException e) { + return response.setStatus(404, "Failed to parse query: " + e.getMessage()); + } + if (query.getCount().isPresent()) { + return response.setStatus(400, "$count not allowed on delete requests."); + } + if (!query.getExpand().isEmpty()) { + return response.setStatus(400, "$expand not allowed on delete requests."); + } + if (!query.getOrderBy().isEmpty()) { + return response.setStatus(400, "$orderby not allowed on delete requests."); + } + if (query.getTop().isPresent()) { + return response.setStatus(400, "$top not allowed on delete requests."); + } + if (query.getSkip().isPresent()) { + return response.setStatus(400, "$skip not allowed on delete requests."); + } + + try { + pm.delete(path, query); + maybeCommitAndClose(); + response.setCode(200); + } catch (NoSuchEntityException e) { + pm.rollbackAndClose(); + response.setStatus(404, e.getMessage()); + } + return response; + } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequest.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequest.java index 9e75a95f3..3beeca7e4 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequest.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequest.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.service; @@ -86,8 +87,8 @@ public String getUrlQuery() { public void setUrl(String url) { if (url.contains("?")) { - this.urlPath = url.substring(0, url.lastIndexOf("?")); - this.urlQuery = url.substring(url.indexOf("?") + 1); + this.urlPath = url.substring(0, url.lastIndexOf('?')); + this.urlQuery = url.substring(url.indexOf('?') + 1); } else { this.urlPath = url; this.urlQuery = null; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequestBuilder.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequestBuilder.java index 7ee237956..603f4781f 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequestBuilder.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceRequestBuilder.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.service; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceResponse.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceResponse.java index f0793d5c1..7640e6f7e 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceResponse.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/service/ServiceResponse.java @@ -1,17 +1,18 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.service; diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/BusSettings.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/BusSettings.java new file mode 100644 index 000000000..430df5240 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/BusSettings.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 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.sta.settings; + +/** + * + * @author jab + */ +public class BusSettings { + + /** + * Tags + */ + private static final String TAG_IMPLEMENTATION_CLASS = "busImplementationClass"; + + /** + * Default values + */ + private static final String DEFAULT_IMPLEMENTATION_CLASS = "de.fraunhofer.iosb.ilt.sta.messagebus.InternalMessageBus"; + + /** + * Fully-qualified class name of the MqttServer implementation class + */ + private String busImplementationClass; + + /** + * Extension point for implementation specific settings + */ + private Settings customSettings; + + public BusSettings(Settings settings) { + if (settings == null) { + throw new IllegalArgumentException("settings most be non-null"); + } + init(settings); + } + + private void init(Settings settings) { + busImplementationClass = settings.getWithDefault(TAG_IMPLEMENTATION_CLASS, DEFAULT_IMPLEMENTATION_CLASS, String.class); + customSettings = settings; + } + + public String getBusImplementationClass() { + return busImplementationClass; + } + + public void setBusImplementationClass(String busImplementationClass) { + if (busImplementationClass == null || busImplementationClass.isEmpty()) { + throw new IllegalArgumentException(TAG_IMPLEMENTATION_CLASS + " must be non-empty"); + } + try { + Class.forName(busImplementationClass, false, this.getClass().getClassLoader()); + } catch (ClassNotFoundException ex) { + throw new IllegalArgumentException(TAG_IMPLEMENTATION_CLASS + " '" + busImplementationClass + "' could not be found", ex); + } + this.busImplementationClass = busImplementationClass; + } + + public Settings getCustomSettings() { + return customSettings; + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/CoreSettings.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/CoreSettings.java index a9c3ad80c..88bd90159 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/CoreSettings.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/CoreSettings.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.Reader; +import java.net.URI; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Paths; @@ -38,43 +39,64 @@ public class CoreSettings { * Tags */ public static final String TAG_API_VERSION = "ApiVersion"; - private static final String TAG_DEFAULT_COUNT = "defaultCount"; - private static final String TAG_DEFAULT_TOP = "defaultTop"; - private static final String TAG_MAX_TOP = "maxTop"; - private static final String TAG_MAX_DATASIZE = "maxDataSize"; + public static final String TAG_DEFAULT_COUNT = "defaultCount"; + public static final String TAG_DEFAULT_TOP = "defaultTop"; + public static final String TAG_MAX_TOP = "maxTop"; + public static final String TAG_MAX_DATASIZE = "maxDataSize"; public static final String TAG_SERVICE_ROOT_URL = "serviceRootUrl"; - private static final String TAG_USE_ABSOLUTE_NAVIGATION_LINKS = "useAbsoluteNavigationLinks"; - private static final String TAG_TEMP_PATH = "tempPath"; + public static final String TAG_USE_ABSOLUTE_NAVIGATION_LINKS = "useAbsoluteNavigationLinks"; + public static final String TAG_TEMP_PATH = "tempPath"; + + // HTTP Tags + public static final String TAG_CORS_ENABLE = "cors.enable"; + public static final String TAG_CORS_ALLOWED_ORIGINS = "cors.allowed.origins"; + public static final String TAG_CORS_ALLOWED_METHODS = "cors.allowed.methods"; + public static final String TAG_CORS_EXPOSED_HEADERS = "cors.exposed.headers"; + public static final String TAG_CORS_ALLOWED_HEADERS = "cors.allowed.headers"; + public static final String TAG_CORS_SUPPORT_CREDENTIALS = "cors.support.credentials"; + public static final String TAG_CORS_PREFLIGHT_MAXAGE = "cors.preflight.maxage"; + public static final String TAG_CORS_REQUEST_DECORATE = "cors.request.decorate"; /** * Defaults */ - private static final String DEFAULT_API_VERSION = "v1.0"; - private static final int DEFAULT_MAX_TOP = 100; - private static final long DEFAULT_MAX_DATASIZE = 25000000; - private static final boolean DEFAULT_COUNT = true; - private static final boolean DEFAULT_USE_ABSOLUTE_NAVIGATION_LINKS = true; + public static final String DEFAULT_API_VERSION = "v1.0"; + public static final int DEFAULT_MAX_TOP = 100; + public static final long DEFAULT_MAX_DATASIZE = 25000000; + public static final boolean DEFAULT_COUNT = true; + public static final boolean DEFAULT_USE_ABSOLUTE_NAV_LINKS = true; + // HTTP Defaults + public static final boolean DEFAULT_CORS_ENABLE = false; + public static final String DEFAULT_CORS_ALLOWED_ORIGINS = "*"; + public static final String DEFAULT_CORS_ALLOWED_METHODS = "GET,HEAD,OPTIONS"; + public static final String DEFAULT_CORS_EXPOSED_HEADERS = "Location"; + public static final String DEFAULT_CORS_ALLOWED_HEADERS = "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization"; + public static final String DEFAULT_CORS_SUPPORT_CREDENTIALS = "false"; + public static final String DEFAULT_CORS_PREFLIGHT_MAXAGE = "1800"; + public static final String DEFAULT_CORS_REQUEST_DECORATE = "true"; /** * Prefixes */ - private static final String PREFIX_MQTT = "mqtt."; - private static final String PREFIX_PERSISTENCE = "persistence."; + public static final String PREFIX_BUS = "bus."; + public static final String PREFIX_MQTT = "mqtt."; + public static final String PREFIX_HTTP = "http."; + public static final String PREFIX_PERSISTENCE = "persistence."; /** - * Root URL of the service to run + * Root URL of the service to run. */ private String serviceRootUrl; /** - * API Version + * API Version. */ private String apiVersion = DEFAULT_API_VERSION; /** - * Root URL of the service to run + * Root URL of the service to run. */ - private boolean useAbsoluteNavigationLinks = DEFAULT_USE_ABSOLUTE_NAVIGATION_LINKS; + private boolean useAbsoluteNavigationLinks = DEFAULT_USE_ABSOLUTE_NAV_LINKS; /** * The default top to use when no specific top is set. @@ -93,17 +115,129 @@ public class CoreSettings { */ private boolean countDefault = DEFAULT_COUNT; /** - * Path to temp folder + * Path to temp folder. */ private String tempPath; /** - * The MQTT settings to use + * The MQTT settings to use. */ private MqttSettings mqttSettings; /** - * The Persistence settings to use + * The message bus settings to use. + */ + private BusSettings busSettings; + /** + * The Persistence settings to use. */ private PersistenceSettings persistenceSettings; + /** + * The HTTP settings to use. + */ + private Settings httpSettings; + + /** + * Creates an empty, uninitialised CoreSettings. + */ + public CoreSettings() { + // Nothing here + } + + /** + * Creates a new CoreSettings and initialises it with the given properties, + * and environment variables. + * + * @param properties The properties to use. Environment variables can + * override these. + */ + public CoreSettings(Properties properties) { + if (properties == null) { + throw new IllegalArgumentException("properties must be non-null"); + } + init(properties); + } + + private void init(Properties properties) { + Settings settings = new Settings(properties); + if (!settings.containsName(TAG_SERVICE_ROOT_URL)) { + throw new IllegalArgumentException(getClass().getName() + " must contain property '" + TAG_SERVICE_ROOT_URL + "'"); + } + if (!settings.containsName(TAG_TEMP_PATH)) { + throw new IllegalArgumentException(getClass().getName() + " must contain property '" + TAG_TEMP_PATH + "'"); + } + tempPath = settings.get(TAG_TEMP_PATH); + if (tempPath == null || tempPath.isEmpty()) { + throw new IllegalArgumentException("tempPath must be non-empty"); + } + try { + if (!Paths.get(tempPath).toRealPath(LinkOption.NOFOLLOW_LINKS).toFile().exists()) { + throw new IllegalArgumentException("tempPath '" + tempPath + "' does not exist"); + } + } catch (IOException exc) { + LOGGER.error("Failed to find tempPath: {}.", tempPath); + throw new IllegalArgumentException("tempPath '" + tempPath + "' does not exist", exc); + } + apiVersion = settings.getWithDefault(TAG_API_VERSION, DEFAULT_API_VERSION, String.class); + serviceRootUrl = URI.create(settings.get(CoreSettings.TAG_SERVICE_ROOT_URL) + "/" + apiVersion).normalize().toString(); + useAbsoluteNavigationLinks = settings.getWithDefault(TAG_USE_ABSOLUTE_NAVIGATION_LINKS, DEFAULT_USE_ABSOLUTE_NAV_LINKS, Boolean.class); + countDefault = settings.getWithDefault(TAG_DEFAULT_COUNT, DEFAULT_COUNT, Boolean.class); + topDefault = settings.getWithDefault(TAG_DEFAULT_TOP, DEFAULT_MAX_TOP, Integer.class); + topMax = settings.getWithDefault(TAG_MAX_TOP, DEFAULT_MAX_TOP, Integer.class); + dataSizeMax = settings.getWithDefault(TAG_MAX_DATASIZE, DEFAULT_MAX_DATASIZE, Long.class); + + mqttSettings = new MqttSettings(new Settings(settings.getProperties(), PREFIX_MQTT, false)); + persistenceSettings = new PersistenceSettings(new Settings(settings.getProperties(), PREFIX_PERSISTENCE, false)); + busSettings = new BusSettings(new Settings(settings.getProperties(), PREFIX_BUS, false)); + httpSettings = new Settings(settings.getProperties(), PREFIX_HTTP, false); + if (mqttSettings.getTopicPrefix() == null || mqttSettings.getTopicPrefix().isEmpty()) { + mqttSettings.setTopicPrefix(apiVersion + "/"); + } + } + + public static CoreSettings load(String file) { + Properties properties = new Properties(); + try (Reader reader = Files.newBufferedReader(Paths.get(file, (String) null))) { + properties.load(reader); + } catch (IOException ex) { + LOGGER.error("error loading properties file, using defaults", ex); + } + return load(properties); + } + + public static CoreSettings load(Properties properties) { + return new CoreSettings(properties); + } + + public BusSettings getBusSettings() { + return busSettings; + } + + public MqttSettings getMqttSettings() { + return mqttSettings; + } + + public Settings getHttpSettings() { + return httpSettings; + } + + public PersistenceSettings getPersistenceSettings() { + return persistenceSettings; + } + + public String getServiceRootUrl() { + return serviceRootUrl; + } + + public boolean isUseAbsoluteNavigationLinks() { + return useAbsoluteNavigationLinks; + } + + public String getApiVersion() { + return apiVersion; + } + + public String getTempPath() { + return tempPath; + } /** * The default top to use when no specific top is set. @@ -181,91 +315,4 @@ public void setCountDefault(boolean countDefault) { this.countDefault = countDefault; } - public CoreSettings() { - - } - - public CoreSettings(Properties properties) { - if (properties == null) { - throw new IllegalArgumentException("properties must be non-null"); - } - init(properties); - } - - public CoreSettings(Properties properties, String serviceRootUrl, String tempPath) { - if (properties == null) { - throw new IllegalArgumentException("properties must be non-null"); - } - properties.setProperty(TAG_SERVICE_ROOT_URL, serviceRootUrl); - properties.setProperty(TAG_TEMP_PATH, tempPath); - init(properties); - } - - private void init(Properties properties) { - Settings settings = new Settings(properties); - if (!settings.contains(TAG_SERVICE_ROOT_URL)) { - throw new IllegalArgumentException(getClass().getName() + " must contain property '" + TAG_SERVICE_ROOT_URL + "'"); - } - if (!settings.contains(TAG_TEMP_PATH)) { - throw new IllegalArgumentException(getClass().getName() + " must contain property '" + TAG_TEMP_PATH + "'"); - } - tempPath = settings.getString(TAG_TEMP_PATH); - if (tempPath == null || tempPath.isEmpty()) { - throw new IllegalArgumentException("tempPath must be non-empty"); - } - if (Files.notExists(Paths.get(tempPath), LinkOption.NOFOLLOW_LINKS)) { - throw new IllegalArgumentException("tempPath '" + tempPath + "' does not exist"); - } - serviceRootUrl = settings.getString(TAG_SERVICE_ROOT_URL); - apiVersion = settings.getWithDefault(TAG_API_VERSION, DEFAULT_API_VERSION, String.class); - useAbsoluteNavigationLinks = settings.getWithDefault(TAG_USE_ABSOLUTE_NAVIGATION_LINKS, DEFAULT_USE_ABSOLUTE_NAVIGATION_LINKS, Boolean.class); - countDefault = settings.getWithDefault(TAG_DEFAULT_COUNT, DEFAULT_COUNT, Boolean.class); - topDefault = settings.getWithDefault(TAG_DEFAULT_TOP, DEFAULT_MAX_TOP, Integer.class); - topMax = settings.getWithDefault(TAG_MAX_TOP, DEFAULT_MAX_TOP, Integer.class); - dataSizeMax = settings.getWithDefault(TAG_MAX_DATASIZE, DEFAULT_MAX_DATASIZE, Long.class); - mqttSettings = new MqttSettings(PREFIX_MQTT, settings.filter(PREFIX_MQTT)); - persistenceSettings = new PersistenceSettings(PREFIX_PERSISTENCE, settings.filter(PREFIX_PERSISTENCE)); - if (mqttSettings.getTopicPrefix() == null || mqttSettings.getTopicPrefix().isEmpty()) { - mqttSettings.setTopicPrefix(apiVersion + "/"); - } - } - - public static CoreSettings load(String file) { - Properties properties = new Properties(); - try (Reader reader = Files.newBufferedReader(Paths.get(file, (String) null))) { - properties.load(reader); - } catch (IOException ex) { - LOGGER.error("error loading properties file, using defaults", ex); - } - return load(properties); - } - - public static CoreSettings load(Properties properties) { - return new CoreSettings(properties); - } - - public MqttSettings getMqttSettings() { - return mqttSettings; - } - - public PersistenceSettings getPersistenceSettings() { - return persistenceSettings; - } - - public String getServiceRootUrl() { - return serviceRootUrl; - } - - public boolean isUseAbsoluteNavigationLinks() { - return useAbsoluteNavigationLinks; - } - - public String getApiVersion() { - return apiVersion; - } - - public String getTempPath() { - return tempPath; - } - } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/MqttSettings.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/MqttSettings.java index 2dd45572e..a57550de2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/MqttSettings.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/MqttSettings.java @@ -17,9 +17,6 @@ */ package de.fraunhofer.iosb.ilt.sta.settings; -import java.util.Arrays; -import java.util.List; - /** * * @author jab @@ -43,6 +40,7 @@ public class MqttSettings { /** * Default values */ + private static final String DEFAULT_IMPLEMENTATION_CLASS = "de.fraunhofer.iosb.ilt.sensorthingsserver.mqtt.moquette.MoquetteMqttServer"; private static final boolean DEFAULT_ENABLE_MQTT = true; private static final int DEFAULT_QOS_LEVEL = 2; private static final int DEFAULT_PORT = 1883; @@ -61,16 +59,7 @@ public class MqttSettings { public static final int MIN_QOS_LEVEL = 0; public static final int MAX_QOS_LEVEL = 2; - private static final List ALL_PROPERTIES = Arrays.asList( - TAG_ENABLED, - TAG_HOST, - TAG_IMPLEMENTATION_CLASS, - TAG_SUBSCRIBE_MESSAGE_QUEUE_SIZE, - TAG_CREATE_MESSAGE_QUEUE_SIZE, - TAG_PORT, - TAG_QOS, - TAG_SUBSCRIBE_THREAD_POOL_SIZE, - TAG_CREATE_THREAD_POOL_SIZE); + private static final String MUST_BE_POSITIVE = " must be > 0"; /** * Fully-qualified class name of the MqttServer implementation class @@ -132,21 +121,15 @@ public class MqttSettings { */ private Settings customSettings; - public MqttSettings(String prefix, Settings settings) { - if (prefix == null || prefix.isEmpty()) { - throw new IllegalArgumentException("prfeix most be non-empty"); - } + public MqttSettings(Settings settings) { if (settings == null) { throw new IllegalArgumentException("settings most be non-null"); } - init(prefix, settings); + init(settings); } - private void init(String prefix, Settings settings) { - if (!settings.contains(TAG_IMPLEMENTATION_CLASS)) { - throw new IllegalArgumentException(getClass().getName() + " must contain property '" + TAG_IMPLEMENTATION_CLASS + "'"); - } - mqttServerImplementationClass = settings.getString(TAG_IMPLEMENTATION_CLASS); + private void init(Settings settings) { + mqttServerImplementationClass = settings.get(TAG_IMPLEMENTATION_CLASS, DEFAULT_IMPLEMENTATION_CLASS); enableMqtt = settings.getWithDefault(TAG_ENABLED, DEFAULT_ENABLE_MQTT, Boolean.class); port = settings.getWithDefault(TAG_PORT, DEFAULT_PORT, Integer.class); setHost(settings.getWithDefault(TAG_HOST, DEFAULT_HOST, String.class)); @@ -156,7 +139,7 @@ private void init(String prefix, Settings settings) { setCreateMessageQueueSize(settings.getWithDefault(TAG_CREATE_MESSAGE_QUEUE_SIZE, DEFAULT_CREATE_MESSAGE_QUEUE_SIZE, Integer.class)); setCreateThreadPoolSize(settings.getWithDefault(TAG_CREATE_THREAD_POOL_SIZE, DEFAULT_CREATE_THREAD_POOL_SIZE, Integer.class)); setQosLevel(settings.getWithDefault(TAG_QOS, DEFAULT_QOS_LEVEL, Integer.class)); - customSettings = settings.filter(x -> !ALL_PROPERTIES.contains(x.replaceFirst(prefix, ""))); + customSettings = settings; } public boolean isEnableMqtt() { @@ -243,14 +226,14 @@ public int getSubscribeThreadPoolSize() { public void setSubscribeMessageQueueSize(int subscribeMessageQueueSize) { if (subscribeMessageQueueSize < 1) { - throw new IllegalArgumentException(TAG_SUBSCRIBE_MESSAGE_QUEUE_SIZE + " must be > 0"); + throw new IllegalArgumentException(TAG_SUBSCRIBE_MESSAGE_QUEUE_SIZE + MUST_BE_POSITIVE); } this.subscribeMessageQueueSize = subscribeMessageQueueSize; } public void setSubscribeThreadPoolSize(int subscribeThreadPoolSize) { if (subscribeThreadPoolSize < 1) { - throw new IllegalArgumentException(TAG_SUBSCRIBE_THREAD_POOL_SIZE + " must be > 0"); + throw new IllegalArgumentException(TAG_SUBSCRIBE_THREAD_POOL_SIZE + MUST_BE_POSITIVE); } this.subscribeThreadPoolSize = subscribeThreadPoolSize; } @@ -285,14 +268,14 @@ public int getCreateThreadPoolSize() { public void setCreateMessageQueueSize(int createMessageQueueSize) { if (createMessageQueueSize < 1) { - throw new IllegalArgumentException(TAG_CREATE_MESSAGE_QUEUE_SIZE + " must be > 0"); + throw new IllegalArgumentException(TAG_CREATE_MESSAGE_QUEUE_SIZE + MUST_BE_POSITIVE); } this.createMessageQueueSize = createMessageQueueSize; } public void setCreateThreadPoolSize(int createThreadPoolSize) { if (createThreadPoolSize < 1) { - throw new IllegalArgumentException(TAG_CREATE_THREAD_POOL_SIZE + " must be > 0"); + throw new IllegalArgumentException(TAG_CREATE_THREAD_POOL_SIZE + MUST_BE_POSITIVE); } this.createThreadPoolSize = createThreadPoolSize; } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/PersistenceSettings.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/PersistenceSettings.java index 27b2faf25..627773aa5 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/PersistenceSettings.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/PersistenceSettings.java @@ -17,9 +17,6 @@ */ package de.fraunhofer.iosb.ilt.sta.settings; -import java.util.Arrays; -import java.util.List; - /** * * @author jab @@ -30,40 +27,37 @@ public class PersistenceSettings { * Tags */ private static final String TAG_IMPLEMENTATION_CLASS = "persistenceManagerImplementationClass"; + private static final String DEFAULT_IMPLEMENTATION_CLASS = "de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong"; private static final String TAG_ALWAYS_ORDERBY_ID = "alwaysOrderbyId"; - - private static final List ALL_PROPERTIES = Arrays.asList( - TAG_IMPLEMENTATION_CLASS, - TAG_ALWAYS_ORDERBY_ID - ); + private static final String TAG_ID_GENERATION_MODE = "idGenerationMode"; + private static final String TAG_AUTO_UPDATE_DATABASE = "autoUpdateDatabase"; + private static final boolean DEFAULT_AUTO_UPDATE_DATABASE = false; /** * Fully-qualified class name of the PersistenceManager implementation class */ private String persistenceManagerImplementationClass; private boolean alwaysOrderbyId = true; + private String idGenerationMode = "ServerGeneratedOnly"; + private boolean autoUpdateDatabase; /** * Extension point for implementation specific settings */ private Settings customSettings; - public PersistenceSettings(String prefix, Settings settings) { - if (prefix == null || prefix.isEmpty()) { - throw new IllegalArgumentException("settings most be non-empty"); - } + public PersistenceSettings(Settings settings) { if (settings == null) { throw new IllegalArgumentException("settings most be non-null"); } - init(prefix, settings); + init(settings); } - private void init(String prefix, Settings settings) { - if (!settings.contains(TAG_IMPLEMENTATION_CLASS)) { - throw new IllegalArgumentException(getClass().getName() + " must contain property '" + TAG_IMPLEMENTATION_CLASS + "'"); - } - persistenceManagerImplementationClass = settings.getString(TAG_IMPLEMENTATION_CLASS); + private void init(Settings settings) { + persistenceManagerImplementationClass = settings.get(TAG_IMPLEMENTATION_CLASS, DEFAULT_IMPLEMENTATION_CLASS); alwaysOrderbyId = settings.getBoolean(TAG_ALWAYS_ORDERBY_ID, alwaysOrderbyId); - customSettings = settings.filter(x -> !ALL_PROPERTIES.contains(x.replaceFirst(prefix, ""))); + idGenerationMode = settings.get(TAG_ID_GENERATION_MODE, idGenerationMode); + autoUpdateDatabase = settings.getBoolean(TAG_AUTO_UPDATE_DATABASE, DEFAULT_AUTO_UPDATE_DATABASE); + customSettings = settings; } public String getPersistenceManagerImplementationClass() { @@ -74,7 +68,15 @@ public boolean getAlwaysOrderbyId() { return alwaysOrderbyId; } + public boolean isAutoUpdateDatabase() { + return autoUpdateDatabase; + } + public Settings getCustomSettings() { return customSettings; } + + public String getIdGenerationMode() { + return idGenerationMode; + } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/Settings.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/Settings.java index 09d7e259b..6b0db544b 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/Settings.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/settings/Settings.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Properties; -import java.util.function.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,60 +29,130 @@ public class Settings { private static final Logger LOGGER = LoggerFactory.getLogger(Settings.class); + private static final String NOT_SET_USING_DEFAULT_VALUE = "Not set {}{}, using default value {}."; + private static final String ERROR_GETTING_SETTINGS_VALUE = "error getting settings value"; + private final Properties properties; private String prefix; - public Settings(Properties properties, String prefix) { - this(properties); - this.prefix = (prefix == null ? "" : prefix); - } - - public boolean contains(String name) { - return properties.containsKey(getPropertyKey(name)); - } + private static Properties addEnvironment(Properties wrapped) { + Map environment = System.getenv(); + Properties wrapper = new Properties(wrapped); - public Settings(Properties properties) { - if (properties == null) { - throw new IllegalArgumentException("properties must be non-null"); + for (Map.Entry entry : environment.entrySet()) { + String key = entry.getKey().replaceAll("_", "."); + LOGGER.debug("Added environment variable: {}", key); + wrapper.setProperty(key, entry.getValue()); } - this.prefix = ""; - this.properties = properties; + return wrapper; } + /** + * Creates a new settings, containing only environment variables. + */ public Settings() { - properties = new Properties(); - this.prefix = ""; + this(new Properties(), "", true); } + /** + * Creates a new settings, containing only environment variables with the + * given prefix. + * + * @param prefix The prefix to use. Only parameters with the given prefix + * are accessed. + */ public Settings(String prefix) { - properties = new Properties(); - this.prefix = prefix; + this(new Properties(), prefix, true); } - public Settings filter(String prefix) { - Settings result = filter(x -> x.startsWith(prefix)); - result.prefix = prefix; - return result; + /** + * Creates a new settings, containing the given properties, and environment + * variables, with no prefix. + * + * @param properties The properties to use. These can be overridden by + * environment variables. + */ + public Settings(Properties properties) { + this(properties, "", true); } - public Settings filter(Predicate filter) { - Settings result = new Settings(prefix); - for (Map.Entry entry : properties.entrySet()) { - if (filter.test(entry.getKey().toString())) { - result.properties.setProperty(entry.getKey().toString(), entry.getValue().toString()); - } + /** + * Creates a new settings, containing the given properties, and environment + * variables, with the given prefix. + * + * @param properties The properties to use. + * @param prefix The prefix to use. + * @param wrapInEnvironment Flag indicating if environment variables can + * override the given properties. + */ + public Settings(Properties properties, String prefix, boolean wrapInEnvironment) { + if (properties == null) { + throw new IllegalArgumentException("properties must be non-null"); + } + if (wrapInEnvironment) { + this.properties = addEnvironment(properties); + } else { + this.properties = properties; } - return result; + this.prefix = (prefix == null ? "" : prefix); + } + + /** + * Get the prefix used in this Settings. + * + * @return The prefix used in this Settings. + */ + public String getPrefix() { + return prefix; } + /** + * Get the properties used in this Settings. This is the properties + * configured when creating this Settings, optionally wrapped in a + * properties containing all environment variables. + * + * @return The properties used in this Settings. + */ + public Properties getProperties() { + return properties; + } + + /** + * Get the key as it is used in the config file or environment variables, + * for the property with the given name. The key is the name, with the + * prefix prepended to it. + * + * @param propertyName The name to get the key for. + * @return prefix + propertyName + */ private String getPropertyKey(String propertyName) { - return prefix + propertyName; + return prefix + propertyName.replaceAll("_", "."); } + /** + * Check if there is a property with the given name. The prefix is prepended + * to the name before lookup. + * + * @param name The name to look up. + * @return True if there is a property with the given name. + */ + public boolean containsName(String name) { + // properties.containsKey ignores properties defaults + String val = properties.getProperty(getPropertyKey(name)); + return val != null; + } + + /** + * Check if the given key is present. Throws a PropertyMissingException if + * not. + * + * @param key The key to look up. + */ private void checkExists(String key) { - if (!properties.containsKey(key)) { - throw new PropertyMissingException(key); + if (properties.getProperty(key) != null) { + return; } + throw new PropertyMissingException(key); } public void set(String name, String value) { @@ -100,80 +169,116 @@ public T get(String name, Class returnType) { } else if (returnType.equals(Boolean.class)) { return returnType.cast(getBoolean(name)); } - return returnType.cast(getString(name)); + return returnType.cast(get(name)); } public T getWithDefault(String name, T defaultValue, Class returnType) { try { return get(name, returnType); } catch (Exception ex) { - LOGGER.error("error getting settings value", ex); - // nothing to do here + LOGGER.info(NOT_SET_USING_DEFAULT_VALUE, prefix, name, defaultValue); + LOGGER.trace(ERROR_GETTING_SETTINGS_VALUE, ex); } return defaultValue; } - public String getString(String name) { + /** + * Get the property with the given name, prefixed with the prefix of this + * properties. + * + * @param name The name of the property to get. The prefix will be prepended + * to this name. + * @return The value of the requested property, or null if the property is + * not found. + */ + public String get(String name) { String key = getPropertyKey(name); checkExists(key); - return properties.get(key).toString(); + String value = properties.getProperty(key); + LOGGER.info("Setting {}{} has value {}.", prefix, name, value); + return value; } - public Object get(String name) { + public String get(String name, String defaultValue) { String key = getPropertyKey(name); - checkExists(key); - return properties.get(key); + String value = properties.getProperty(key); + if (value == null) { + LOGGER.info(NOT_SET_USING_DEFAULT_VALUE, prefix, name, defaultValue); + return defaultValue; + } + LOGGER.info("Setting {}{} has value {}.", prefix, name, value); + return value; } public int getInt(String name) { - String key = getPropertyKey(name); - checkExists(key); try { - return Integer.parseInt(properties.get(key).toString()); + return Integer.parseInt(get(name)); } catch (NumberFormatException ex) { - throw new PropertyTypeException(key, Integer.class, ex); + throw new PropertyTypeException(name, Integer.class, ex); + } + } + + public int getInt(String name, int defaultValue) { + try { + return getInt(name); + } catch (Exception ex) { + LOGGER.info(NOT_SET_USING_DEFAULT_VALUE, prefix, name, defaultValue); + LOGGER.trace(ERROR_GETTING_SETTINGS_VALUE, ex); + return defaultValue; } } public long getLong(String name) { - String key = getPropertyKey(name); - checkExists(key); try { - return Long.parseLong(properties.get(key).toString()); + return Long.parseLong(get(name)); } catch (NumberFormatException ex) { - throw new PropertyTypeException(key, Long.class, ex); + throw new PropertyTypeException(name, Long.class, ex); + } + } + + public long getLong(String name, long defaultValue) { + try { + return getLong(name); + } catch (Exception ex) { + LOGGER.info(NOT_SET_USING_DEFAULT_VALUE, prefix, name, defaultValue); + LOGGER.trace(ERROR_GETTING_SETTINGS_VALUE, ex); + return defaultValue; } } public double getDouble(String name) { - String key = getPropertyKey(name); - checkExists(key); try { - return Double.parseDouble(properties.get(key).toString()); + return Double.parseDouble(get(name)); } catch (NumberFormatException ex) { - throw new PropertyTypeException(key, Double.class, ex); + throw new PropertyTypeException(name, Double.class, ex); } } - public boolean getBoolean(String name) { - String key = getPropertyKey(name); - checkExists(key); + public double getDouble(String name, double defaultValue) { try { - return Boolean.valueOf(properties.get(key).toString()); + return getDouble(name); } catch (Exception ex) { - throw new PropertyTypeException(key, Boolean.class, ex); + LOGGER.info(NOT_SET_USING_DEFAULT_VALUE, prefix, name, defaultValue); + LOGGER.trace(ERROR_GETTING_SETTINGS_VALUE, ex); + return defaultValue; } } - public boolean getBoolean(String name, boolean dflt) { - String key = getPropertyKey(name); - if (!properties.containsKey(key)) { - return dflt; + public boolean getBoolean(String name) { + try { + return Boolean.valueOf(get(name)); + } catch (Exception ex) { + throw new PropertyTypeException(name, Boolean.class, ex); } + } + + public boolean getBoolean(String name, boolean defaultValue) { try { - return Boolean.valueOf(properties.get(key).toString()); + return getBoolean(name); } catch (Exception ex) { - return dflt; + LOGGER.info(NOT_SET_USING_DEFAULT_VALUE, prefix, name, defaultValue); + LOGGER.trace(ERROR_GETTING_SETTINGS_VALUE, ex); + return defaultValue; } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ArrayValueHandlers.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ArrayValueHandlers.java index dd78121b7..f60539dca 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ArrayValueHandlers.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ArrayValueHandlers.java @@ -17,11 +17,11 @@ */ package de.fraunhofer.iosb.ilt.sta.util; -import de.fraunhofer.iosb.ilt.sta.model.builder.FeatureOfInterestBuilder; +import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; import de.fraunhofer.iosb.ilt.sta.model.builder.ObservationBuilder; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; import java.util.HashMap; @@ -47,37 +47,40 @@ public interface ArrayValueHandler { /** * Our default handlers. */ - private static Map HANDLERS; + private static final Map HANDLERS = new HashMap<>(); public static ArrayValueHandler getHandler(String component) { - if (HANDLERS == null) { + if (HANDLERS.isEmpty()) { createDefaults(); } return HANDLERS.get(component); } private static synchronized void createDefaults() { + if (!HANDLERS.isEmpty()) { + return; + } + final IdManager idManager = PersistenceManagerFactory.getInstance().create().getIdManager(); - HANDLERS = new HashMap<>(); - ArrayValueHandler idHandler = (Object value, ObservationBuilder target) -> { - target.setId(idManager.parseId(value.toString())); - }; + ArrayValueHandler idHandler = (Object value, ObservationBuilder target) -> target.setId(idManager.parseId(value.toString())); HANDLERS.put("id", idHandler); HANDLERS.put("@iot.id", idHandler); - HANDLERS.put("result", (ArrayValueHandler) (Object value, ObservationBuilder target) -> { - target.setResult(value); - }); - HANDLERS.put("resultQuality", (ArrayValueHandler) (Object value, ObservationBuilder target) -> { - target.setResultQuality(value); - }); - HANDLERS.put("parameters", (ArrayValueHandler) (Object value, ObservationBuilder target) -> { + HANDLERS.put( + "result", + (Object value, ObservationBuilder target) -> target.setResult(value) + ); + HANDLERS.put( + "resultQuality", + (Object value, ObservationBuilder target) -> target.setResultQuality(value) + ); + HANDLERS.put("parameters", (Object value, ObservationBuilder target) -> { if (value instanceof Map) { target.setParameters((Map) value); return; } throw new IllegalArgumentException("parameters has to be a map."); }); - HANDLERS.put("phenomenonTime", (ArrayValueHandler) (Object value, ObservationBuilder target) -> { + HANDLERS.put("phenomenonTime", (Object value, ObservationBuilder target) -> { try { TimeInstant time = TimeInstant.parse(value.toString()); target.setPhenomenonTime(time); @@ -94,7 +97,7 @@ private static synchronized void createDefaults() { } throw new IllegalArgumentException("phenomenonTime could not be parsed as time instant or time interval."); }); - HANDLERS.put("resultTime", (ArrayValueHandler) (Object value, ObservationBuilder target) -> { + HANDLERS.put("resultTime", (Object value, ObservationBuilder target) -> { try { TimeInstant time = TimeInstant.parse(value.toString()); target.setResultTime(time); @@ -102,7 +105,7 @@ private static synchronized void createDefaults() { throw new IllegalArgumentException("resultTime could not be parsed as time instant or time interval.", e); } }); - HANDLERS.put("validTime", (ArrayValueHandler) (Object value, ObservationBuilder target) -> { + HANDLERS.put("validTime", (Object value, ObservationBuilder target) -> { try { TimeInterval time = TimeInterval.parse(value.toString()); target.setValidTime(time); @@ -110,10 +113,9 @@ private static synchronized void createDefaults() { throw new IllegalArgumentException("resultTime could not be parsed as time instant or time interval.", e); } }); - HANDLERS.put("FeatureOfInterest/id", (ArrayValueHandler) (Object value, ObservationBuilder target) -> { - FeatureOfInterestBuilder foiBuilder = new FeatureOfInterestBuilder(); + HANDLERS.put("FeatureOfInterest/id", (Object value, ObservationBuilder target) -> { Id foiId = idManager.parseId(value.toString()); - target.setFeatureOfInterest(foiBuilder.setId(foiId).build()); + target.setFeatureOfInterest(new FeatureOfInterest(foiId)); }); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/GeoHelper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/GeoHelper.java index 1ad30d46e..63ed390a4 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/GeoHelper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/GeoHelper.java @@ -47,6 +47,8 @@ public class GeoHelper { public static final Pattern WKT_LINE_PATTERN = Pattern.compile(WKT_LINE_REGEX, Pattern.CASE_INSENSITIVE); public static final Pattern WKT_POLYGON_PATTERN = Pattern.compile(WKT_POLYGON_REGEX, Pattern.CASE_INSENSITIVE); + private static final String DOES_NOT_MATCH_PATTERN = "' does not match pattern '"; + private GeoHelper() { } @@ -64,7 +66,7 @@ public static Point parsePoint(String value) { return new Point(Double.parseDouble(coordinates[0]), Double.parseDouble(coordinates[1]), Double.parseDouble(coordinates[2])); } } else { - throw new IllegalArgumentException("'" + value + "' does not match pattern '" + GeoHelper.WKT_POINT_PATTERN.pattern() + "'"); + throw new IllegalArgumentException("'" + value + DOES_NOT_MATCH_PATTERN + GeoHelper.WKT_POINT_PATTERN.pattern() + "'"); } } @@ -75,18 +77,18 @@ public static LineString parseLine(String value) { return new LineString( Arrays.asList(points).stream() .map(x -> Arrays.asList(x.split(" "))) //split each point in coorinates array - .map(x -> x.stream().map(y -> Double.parseDouble(y))) // parse each coordinate to double + .map(x -> x.stream().map(Double::parseDouble)) // parse each coordinate to double .map(x -> getPoint(x.toArray(size -> new Double[size])).getCoordinates()) //collect double coordinate into double[] and convert to Point .toArray(size -> new LngLatAlt[size])); } else { - throw new IllegalArgumentException("'" + value + "' does not match pattern '" + GeoHelper.WKT_POINT_PATTERN.pattern() + "'"); + throw new IllegalArgumentException("'" + value + DOES_NOT_MATCH_PATTERN + GeoHelper.WKT_LINE_PATTERN.pattern() + "'"); } } private static LngLatAlt[] stringListToPoints(String value) { return Arrays.asList(value.split("\\s*,\\s*")).stream() .map(x -> Arrays.asList(x.split(" "))) //split each point in coorinates array - .map(x -> x.stream().map(y -> Double.parseDouble(y))) // parse each coordinate to double + .map(x -> x.stream().map(Double::parseDouble)) // parse each coordinate to double .map(x -> getPoint(x.toArray(size -> new Double[size])).getCoordinates()) //collect double coordinate into double[] and convert to Point .toArray(size -> new LngLatAlt[size]); } @@ -105,13 +107,14 @@ public static Polygon parsePolygon(String value) { } return result; } else { - throw new IllegalArgumentException("'" + value + "' does not match pattern '" + GeoHelper.WKT_POLYGON_PATTERN.pattern() + "'"); + throw new IllegalArgumentException("'" + value + DOES_NOT_MATCH_PATTERN + GeoHelper.WKT_POLYGON_PATTERN.pattern() + "'"); } } public static Point getPoint(T... values) { - assert (values != null); - assert (values.length == 2 || values.length == 3); + if (values == null || values.length < 2 || values.length > 3) { + throw new IllegalArgumentException("values must have a length of 2 or 3."); + } if (values.length == 2) { return new Point(values[0].doubleValue(), values[1].doubleValue()); } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/GitVersionInfo.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/GitVersionInfo.java new file mode 100644 index 000000000..0edc454c1 --- /dev/null +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/GitVersionInfo.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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.sta.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author scf + */ +public class GitVersionInfo { + + private GitVersionInfo() { + // Utility class, not to be instantiated. + } + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(GitVersionInfo.class); + + /** + * A type reference for Map<String, String>. + */ + public static final TypeReference TYPE_MAP_STRING_STRING = new TypeReference>() { + // Empty on purpose. + }; + public static final String PACKAGE_NAME = "FROST-Server.Core"; + + private static Map gitData; + + /** + * Outputs the git version info to the log. + */ + public static void logGitInfo() { + init(); + LOGGER.info("{} Version: {}", PACKAGE_NAME, gitData.get("git.commit.id.describe")); + } + + private static void init() { + if (gitData == null) { + gitData = readInfo(); + } + } + + private static Map readInfo() { + ClassLoader classLoader = GitVersionInfo.class.getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream("git.json"); + ObjectMapper mapper = new ObjectMapper(); + Map map; + try { + map = mapper.readValue(inputStream, TYPE_MAP_STRING_STRING); + } catch (IOException exc) { + LOGGER.error("Failed to read git info file.", exc); + map = new HashMap<>(); + } + return map; + } +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/IncompleteEntityException.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/IncompleteEntityException.java index 27ac0ba38..61017121a 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/IncompleteEntityException.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/IncompleteEntityException.java @@ -21,7 +21,7 @@ * * @author Hylke van der Schaaf */ -public class IncompleteEntityException extends RuntimeException { +public class IncompleteEntityException extends Exception { public IncompleteEntityException() { } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ParserHelper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ParserHelper.java index a3142b1e0..78b9a1224 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ParserHelper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ParserHelper.java @@ -27,11 +27,7 @@ import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; import de.fraunhofer.iosb.ilt.sta.query.Query; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.util.Objects; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * @@ -39,11 +35,6 @@ */ public class ParserHelper { - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(ParserHelper.class); - public static final class PathQuery { public final ResourcePath path; @@ -56,10 +47,7 @@ public PathQuery(ResourcePath path, Query query) { @Override public int hashCode() { - int hash = 7; - hash = 97 * hash + Objects.hashCode(this.path); - hash = 97 * hash + Objects.hashCode(this.query); - return hash; + return Objects.hash(path, query); } @Override @@ -71,10 +59,8 @@ public boolean equals(Object obj) { return false; } final PathQuery other = (PathQuery) obj; - if (!Objects.equals(this.path, other.path)) { - return false; - } - return Objects.equals(this.query, other.query); + return Objects.equals(this.path, other.path) + && Objects.equals(this.query, other.query); } } @@ -85,24 +71,21 @@ private ParserHelper() { public static Property parseProperty(String propertyName, Property previous) { String decodedName; - try { - decodedName = URLDecoder.decode(propertyName, "UTF-8"); - } catch (UnsupportedEncodingException ex) { - LOGGER.error("UTF-8 is not a supported encoding?!", ex); - throw new IllegalStateException(ex); - } + decodedName = UrlHelper.urlDecode(propertyName); if (previous instanceof EntityProperty || previous instanceof CustomProperty) { return new CustomProperty(decodedName); } NavigationProperty navProp = null; try { navProp = NavigationProperty.fromString(decodedName); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException exc) { + // Not a navigationProperty } EntityProperty entityProp = null; try { entityProp = EntityProperty.fromString(decodedName); - } catch (IllegalArgumentException e2) { + } catch (IllegalArgumentException exc) { + // Not an entityProperty } if (navProp != null && entityProp != null) { char first = decodedName.charAt(0); diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/PathHelper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/PathHelper.java index c94a3f1c3..b119d8051 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/PathHelper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/PathHelper.java @@ -1,23 +1,26 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.util; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import java.util.EnumMap; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,90 +34,73 @@ public class PathHelper { * The logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(PathHelper.class); + private static final Map> navigationMap = new EnumMap<>(EntityType.class); + + static { + Map navPropsForType = getNavPropsForType(EntityType.DATASTREAM); + navPropsForType.put(EntityType.SENSOR, NavigationProperty.SENSOR); + navPropsForType.put(EntityType.OBSERVEDPROPERTY, NavigationProperty.OBSERVEDPROPERTY); + navPropsForType.put(EntityType.OBSERVATION, NavigationProperty.OBSERVATIONS); + navPropsForType.put(EntityType.THING, NavigationProperty.THING); + + navPropsForType = getNavPropsForType(EntityType.MULTIDATASTREAM); + navPropsForType.put(EntityType.SENSOR, NavigationProperty.SENSOR); + navPropsForType.put(EntityType.OBSERVEDPROPERTY, NavigationProperty.OBSERVEDPROPERTIES); + navPropsForType.put(EntityType.OBSERVATION, NavigationProperty.OBSERVATIONS); + navPropsForType.put(EntityType.THING, NavigationProperty.THING); + + navPropsForType = getNavPropsForType(EntityType.THING); + navPropsForType.put(EntityType.HISTORICALLOCATION, NavigationProperty.HISTORICALLOCATIONS); + navPropsForType.put(EntityType.LOCATION, NavigationProperty.LOCATIONS); + navPropsForType.put(EntityType.DATASTREAM, NavigationProperty.DATASTREAMS); + navPropsForType.put(EntityType.MULTIDATASTREAM, NavigationProperty.MULTIDATASTREAMS); + + navPropsForType = getNavPropsForType(EntityType.LOCATION); + navPropsForType.put(EntityType.THING, NavigationProperty.THINGS); + navPropsForType.put(EntityType.HISTORICALLOCATION, NavigationProperty.HISTORICALLOCATIONS); + + navPropsForType = getNavPropsForType(EntityType.HISTORICALLOCATION); + navPropsForType.put(EntityType.THING, NavigationProperty.THINGS); + navPropsForType.put(EntityType.LOCATION, NavigationProperty.LOCATIONS); + + navPropsForType = getNavPropsForType(EntityType.SENSOR); + navPropsForType.put(EntityType.DATASTREAM, NavigationProperty.DATASTREAMS); + navPropsForType.put(EntityType.MULTIDATASTREAM, NavigationProperty.MULTIDATASTREAMS); + + navPropsForType = getNavPropsForType(EntityType.OBSERVEDPROPERTY); + navPropsForType.put(EntityType.DATASTREAM, NavigationProperty.DATASTREAMS); + navPropsForType.put(EntityType.MULTIDATASTREAM, NavigationProperty.MULTIDATASTREAMS); + + navPropsForType = getNavPropsForType(EntityType.OBSERVATION); + navPropsForType.put(EntityType.DATASTREAM, NavigationProperty.DATASTREAM); + navPropsForType.put(EntityType.MULTIDATASTREAM, NavigationProperty.MULTIDATASTREAM); + navPropsForType.put(EntityType.FEATUREOFINTEREST, NavigationProperty.FEATUREOFINTEREST); + + navPropsForType = getNavPropsForType(EntityType.FEATUREOFINTEREST); + navPropsForType.put(EntityType.OBSERVATION, NavigationProperty.OBSERVATIONS); + } private PathHelper() { + // Utility class, not to be instantiated. + } + private static Map getNavPropsForType(EntityType source) { + return navigationMap.computeIfAbsent( + source, + t -> new EnumMap<>(EntityType.class) + ); } public static NavigationProperty getNavigationProperty(EntityType source, EntityType destination) { - switch (source) { - case Datastream: - switch (destination) { - case Sensor: - return NavigationProperty.Sensor; - case ObservedProperty: - return NavigationProperty.ObservedProperty; - case Observation: - return NavigationProperty.Observations; - case Thing: - return NavigationProperty.Thing; - } - case MultiDatastream: - switch (destination) { - case Sensor: - return NavigationProperty.Sensor; - case ObservedProperty: - return NavigationProperty.ObservedProperties; - case Observation: - return NavigationProperty.Observations; - case Thing: - return NavigationProperty.Thing; - } - case Thing: - switch (destination) { - case HistoricalLocation: - return NavigationProperty.HistoricalLocations; - case Location: - return NavigationProperty.Location; - case Datastream: - return NavigationProperty.Datastreams; - case MultiDatastream: - return NavigationProperty.MultiDatastreams; - } - case Location: - switch (destination) { - case Thing: - return NavigationProperty.Things; - case HistoricalLocation: - return NavigationProperty.HistoricalLocations; - } - case HistoricalLocation: - switch (destination) { - case Thing: - return NavigationProperty.Things; - case Location: - return NavigationProperty.Location; - } - case Sensor: - switch (destination) { - case Datastream: - return NavigationProperty.Datastreams; - case MultiDatastream: - return NavigationProperty.MultiDatastreams; - } - case ObservedProperty: - switch (destination) { - case Datastream: - return NavigationProperty.Datastreams; - case MultiDatastream: - return NavigationProperty.MultiDatastreams; - } - case Observation: - switch (destination) { - case MultiDatastream: - return NavigationProperty.MultiDatastream; - case Datastream: - return NavigationProperty.Datastream; - case FeatureOfInterest: - return NavigationProperty.FeatureOfInterest; - } - case FeatureOfInterest: - switch (destination) { - case Observation: - return NavigationProperty.Observations; - } + Map destMap = navigationMap.get(source); + if (destMap == null) { + LOGGER.error("Unknown entity type: {}.", source); + return null; + } + NavigationProperty navProp = destMap.get(destination); + if (navProp == null) { + LOGGER.error("No link known between {} and {}.", source, destination); } - LOGGER.warn("No link known between {} and {}.", source, destination); - return null; + return navProp; } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ProcessorHelper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ProcessorHelper.java index 28dd08158..fbac8f492 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ProcessorHelper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/ProcessorHelper.java @@ -1,24 +1,27 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.util; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.slf4j.Logger; @@ -37,9 +40,8 @@ private ProcessorHelper() { } public static ExecutorService createProcessors(int threadCount, BlockingQueue queue, Consumer consumer, String name) { - ThreadGroup threadGroup = new ThreadGroup(name + "-ThreadGroup"); - ExecutorService result = Executors.newFixedThreadPool(threadCount, - (Runnable r) -> new Thread(threadGroup, r, name + "-Thread")); + ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat(name + "-%d").build(); + ExecutorService result = Executors.newFixedThreadPool(threadCount, factory); for (int i = 0; i < threadCount; i++) { result.submit(new Processor(queue, consumer, name)); @@ -66,16 +68,17 @@ public static void shutdownProcessors(ExecutorService executorService, BlockingQ } } catch (InterruptedException ie) { executorService.shutdownNow(); + Thread.currentThread().interrupt(); } } } - static class Processor implements Runnable { + private static class Processor implements Runnable { - final Logger LOGGER = LoggerFactory.getLogger(Processor.class); - final BlockingQueue queue; - final Consumer consumer; - final String name; + private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class); + private final BlockingQueue queue; + private final Consumer consumer; + private final String name; private Processor(BlockingQueue queue, Consumer consumer) { this(queue, consumer, null); @@ -106,7 +109,8 @@ public void run() { event = queue.take(); consumer.accept(event); } catch (InterruptedException ex) { - LOGGER.debug(name + " interrupted", ex); + LOGGER.debug("{} interrupted", name); + LOGGER.trace(name + " interrupted", ex); Thread.currentThread().interrupt(); break; } catch (Exception ex) { diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/StringHelper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/StringHelper.java index 4df218322..f15a3aeee 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/StringHelper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/StringHelper.java @@ -17,17 +17,27 @@ */ package de.fraunhofer.iosb.ilt.sta.util; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + /** * * @author jab */ public class StringHelper { - private StringHelper() { + public static final Charset ENCODING = StandardCharsets.UTF_8; + private StringHelper() { + // Utility class, not to be instantiated. } public static String capitalize(String string) { return string.substring(0, 1).toUpperCase() + string.substring(1); } + + public static String deCapitalize(String string) { + return string.substring(0, 1).toLowerCase() + string.substring(1); + } + } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/ASTNamedElement.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/UpgradeFailedException.java similarity index 54% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/ASTNamedElement.java rename to FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/UpgradeFailedException.java index 0bc240b13..e28506305 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/parser/query/ASTNamedElement.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/UpgradeFailedException.java @@ -15,44 +15,29 @@ * 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.sta.parser.query; +package de.fraunhofer.iosb.ilt.sta.util; /** - * An ID. + * The exception that should be thrown by a PersistenceManager when upgrading + * the database fails. + * + * @author Hylke van der Schaaf */ -public class ASTNamedElement extends SimpleNode { - - private String name; +public class UpgradeFailedException extends Exception { - /** - * Constructor. - * - * @param id the id - */ - public ASTNamedElement(int id) { - super(id); + public UpgradeFailedException() { } - /** - * Set the name. - * - * @param n the name - */ - public void setName(String n) { - name = n; + public UpgradeFailedException(String message) { + super(message); } - @Override - public String toString() { - if (value == null) { - return "Element: " + name; - } - return "Element: " + name + ": " + value; + public UpgradeFailedException(Throwable cause) { + super(cause); } - @Override - public Object jjtAccept(ParserVisitor visitor, Object data) { - return visitor.visit(this, data); + public UpgradeFailedException(String message, Throwable cause) { + super(message, cause); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelper.java index ccf499bed..8dab52ec2 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelper.java @@ -21,10 +21,10 @@ import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.query.Query; +import static de.fraunhofer.iosb.ilt.sta.util.StringHelper.ENCODING; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,40 +39,87 @@ public class UrlHelper { * The logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(UrlHelper.class); + private static final String UTF8_NOT_SUPPORTED = "UTF-8 not supported?"; private UrlHelper() { // Should not be instantiated. } - public static String generateNextLink(ResourcePath path, Query query) { - int oldSkip = 0; - if (query.getSkip().isPresent()) { - oldSkip = query.getSkip().get(); - } - int top = query.getTopOrDefault(); - int newSkip = oldSkip + top; - query.setSkip(newSkip); - String nextLink = path.toString() + "?" + query.toString(false); - query.setSkip(oldSkip); - return nextLink; + /** + * Replaces all ' in the string with ''. + * + * @param in The string to escape. + * @return The escaped string. + */ + public static String escapeForStringConstant(String in) { + return in.replaceAll("'", "''"); } - public static String urlEncode(String link) { + public static String urlEncode(String input) { try { - return URLEncoder.encode(link, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException ex) { - LOGGER.error("Should not happen, UTF-8 should always be supported.", ex); + return URLEncoder.encode(input, ENCODING.name()); + } catch (UnsupportedEncodingException exc) { + // Should never happen, UTF-8 is build in. + LOGGER.error(UTF8_NOT_SUPPORTED, exc); + throw new IllegalStateException(UTF8_NOT_SUPPORTED, exc); } - return link; } - public static String urlDecode(String link) { + /** + * Decode the given input using UTF-8 as character set. + * + * @param input The input to urlDecode. + * @return The decoded input. + */ + public static String urlDecode(String input) { try { - return URLDecoder.decode(link, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException ex) { - LOGGER.error("Should not happen, UTF-8 should always be supported.", ex); + return URLDecoder.decode(input, ENCODING.name()); + } catch (UnsupportedEncodingException exc) { + // Should never happen, UTF-8 is build in. + LOGGER.error(UTF8_NOT_SUPPORTED, exc); + throw new IllegalStateException(UTF8_NOT_SUPPORTED, exc); + } + } + + /** + * Urlencodes the given string, optionally not encoding forward slashes. + * + * In urls, forward slashes before the "?" must never be urlEncoded. + * Urlencoding of slashes could otherwise be used to obfuscate phising URLs. + * + * @param string The string to urlEncode. + * @param notSlashes If true, forward slashes are not encoded. + * @return The urlEncoded string. + */ + public static String urlEncode(String string, boolean notSlashes) { + if (notSlashes) { + return urlEncodeNotSlashes(string); + } + return urlEncode(string); + } + + /** + * Urlencodes the given string, except for the forward slashes. + * + * @param string The string to urlEncode. + * @return The urlEncoded string. + */ + public static String urlEncodeNotSlashes(String string) { + String[] split = string.split("/"); + for (int i = 0; i < split.length; i++) { + split[i] = urlEncode(split[i]); } - return link; + return String.join("/", split); + } + + public static String generateNextLink(ResourcePath path, Query query) { + int oldSkip = query.getSkip(0); + int top = query.getTopOrDefault(); + int newSkip = oldSkip + top; + query.setSkip(newSkip); + String nextLink = path.toString() + "?" + query.toString(false); + query.setSkip(oldSkip); + return nextLink; } public static String generateSelfLink(String serviceRootUrl, Entity entity) { @@ -101,7 +148,7 @@ public static String generateSelfLink(ResourcePath path, Entity entity) { * @return A navigation link. */ public static String generateNavLink(ResourcePath path, Entity parent, Entity entity, boolean absolute) { - String result = generateSelfLink(path, parent) + "/" + entity.getEntityType().name; + String result = generateSelfLink(path, parent) + "/" + entity.getEntityType().entityName; if (!absolute) { String curPath = path.getServiceRootUrl() + path.getPathUrl(); result = getRelativePath(result, curPath); @@ -142,12 +189,12 @@ public static String getRelativePath(final String targetPath, final String baseP // First get all the common elements. Store them as a string, // and also count how many of them there are. - String common = ""; + StringBuilder common = new StringBuilder(); int commonIndex = 0; int baseLength = baseIsDir ? base.length : base.length - 1; for (int i = 0; i < target.length && i < baseLength; i++) { if (target[i].equals(base[i])) { - common += target[i] + pathSeparator; + common.append(target[i]).append(pathSeparator); commonIndex++; } else { break; @@ -158,10 +205,9 @@ public static String getRelativePath(final String targetPath, final String baseP return targetPath; } - String relative = ""; + StringBuilder relative = new StringBuilder(); if (base.length == commonIndex) { - // Comment this out if you prefer that a relative path not start with ./ - relative = "." + pathSeparator; + relative = new StringBuilder("." + pathSeparator); } else { int numDirsUp = base.length - commonIndex - (targetIsDir ? 0 : 1); // The number of directories we have to backtrack is the length of @@ -169,15 +215,13 @@ public static String getRelativePath(final String targetPath, final String baseP // one because the last element in the path isn't a directory. for (int i = 1; i <= (numDirsUp); i++) { - relative += ".." + pathSeparator; + relative.append("..").append(pathSeparator); } } - //if we are comparing directories then we if (targetPath.length() > common.length()) { - //it's OK, it isn't a directory - relative += targetPath.substring(common.length()); + relative.append(targetPath.substring(common.length())); } - return relative; + return relative.toString(); } } diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/VisibilityHelper.java b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/VisibilityHelper.java index 8c56fa98a..dc903f9b8 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/VisibilityHelper.java +++ b/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/VisibilityHelper.java @@ -19,7 +19,6 @@ import de.fraunhofer.iosb.ilt.sta.model.core.Entity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.NavigableElement; import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; @@ -27,13 +26,12 @@ import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.query.Expand; import de.fraunhofer.iosb.ilt.sta.query.Query; -import java.util.HashMap; +import java.util.EnumMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.stream.Collectors; /** * @@ -41,32 +39,35 @@ */ public class VisibilityHelper { - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(VisibilityHelper.class); - private static class Visibility { Set allProperties = new HashSet<>(); Set visibleProperties = new HashSet<>(); + Set visiblePropertyNames; Set navLinkProperties = new HashSet<>(); - Set invisibleProperties = new HashSet<>(); - Map expandVisibility = new HashMap<>(); + Map expandVisibility = new EnumMap<>(NavigationProperty.class); + + public Set getVisiblePropertyNames() { + if (visiblePropertyNames == null) { + visiblePropertyNames = visibleProperties + .stream() + .map(Property::getJsonName) + .collect(Collectors.toSet()); + } + return visiblePropertyNames; + } /** * Merge the other visibility into this one. * - * TODO: Currently takes the most visible version. + * Currently takes the most visible version. * * @param other The visibility to merge into this one. */ public void merge(Visibility other) { + visiblePropertyNames = null; visibleProperties.addAll(other.visibleProperties); navLinkProperties.addAll(other.navLinkProperties); - for (Property p : other.visibleProperties) { - invisibleProperties.remove(p); - } for (Map.Entry otherSet : other.expandVisibility.entrySet()) { NavigationProperty otherSubProp = otherSet.getKey(); Visibility otherSubVis = otherSet.getValue(); @@ -87,7 +88,7 @@ public static void applyVisibility(Entity entity, ResourcePath path, Query query if (path.isRef()) { Set select = query.getSelect(); select.clear(); - select.add(EntityProperty.SelfLink); + select.add(EntityProperty.SELFLINK); } Visibility v = createVisibility(entity.getEntityType(), query, true); applyVisibility(entity, path, v, useAbsoluteNavigationLinks); @@ -100,7 +101,7 @@ public static void applyVisibility(EntitySet entitySet, Resour if (path.isRef()) { Set select = query.getSelect(); select.clear(); - select.add(EntityProperty.SelfLink); + select.add(EntityProperty.SELFLINK); } EntityType type = entitySet.asList().get(0).getEntityType(); Visibility v = createVisibility(type, query, true); @@ -108,21 +109,24 @@ public static void applyVisibility(EntitySet entitySet, Resour } private static void applyVisibility(Entity e, ResourcePath path, Visibility v, boolean useAbsoluteNavigationLinks) { - e.setSelfLink(UrlHelper.generateSelfLink(path, e)); + if (e.getId() != null) { + e.setSelfLink(UrlHelper.generateSelfLink(path, e)); + } for (Property p : v.navLinkProperties) { Object child = e.getProperty(p); if (child instanceof Entity) { - Entity property = (Entity) child; - property.setNavigationLink(UrlHelper.generateNavLink(path, e, property, useAbsoluteNavigationLinks)); + Entity childEntity = (Entity) child; + childEntity.setNavigationLink(UrlHelper.generateNavLink(path, e, childEntity, useAbsoluteNavigationLinks)); } else if (child instanceof EntitySet) { - EntitySet property = (EntitySet) child; - property.setNavigationLink(UrlHelper.generateNavLink(path, e, property, useAbsoluteNavigationLinks)); + EntitySet childSet = (EntitySet) child; + childSet.setNavigationLink(UrlHelper.generateNavLink(path, e, childSet, useAbsoluteNavigationLinks)); } } for (Map.Entry es : v.expandVisibility.entrySet()) { Object property = e.getProperty(es.getKey()); if (property instanceof Entity) { Entity entity = (Entity) property; + entity.setExportObject(true); applyVisibility(entity, path, es.getValue(), useAbsoluteNavigationLinks); } else if (property instanceof EntitySet) { EntitySet entitySet = (EntitySet) property; @@ -130,16 +134,7 @@ private static void applyVisibility(Entity e, ResourcePath path, Visibility v, b entitySet.setExportObject(true); } } - for (Property p : v.invisibleProperties) { - if (p instanceof EntityProperty) { - e.unsetProperty(p); - } else { - NavigableElement element = (NavigableElement) e.getProperty(p); - if (element != null) { - element.setExportObject(false); - } - } - } + e.setSelectedPropertyNames(v.getVisiblePropertyNames()); } private static void applyVisibility(EntitySet es, ResourcePath path, Visibility v, boolean useAbsoluteNavigationLinks) { @@ -151,73 +146,63 @@ private static void applyVisibility(EntitySet es, ResourcePath private static Visibility createVisibility(EntityType entityType, Query query, boolean topLevel) { Visibility v = new Visibility(); Set properties = entityType.getPropertySet(); - for (Property property : properties) { - v.allProperties.add(property); - } - - copyNavigationProperties2(v.allProperties, v.invisibleProperties); + v.allProperties.addAll(properties); if (query == null || query.getSelect().isEmpty()) { - copyEntityProperties(v.allProperties, v.visibleProperties); if (topLevel) { + v.visibleProperties.addAll(v.allProperties); copyNavigationProperties(v.allProperties, v.navLinkProperties); + } else { + copyEntityProperties(v.allProperties, v.visibleProperties); } } - if (query != null) { - if (!query.getSelect().isEmpty()) { - copyEntityProperties(v.allProperties, v.invisibleProperties); - for (Property select : query.getSelect()) { - if (select instanceof NavigationProperty) { - v.navLinkProperties.add((NavigationProperty) select); - } else { - v.visibleProperties.add(select); - v.invisibleProperties.remove(select); - } + if (query == null) { + return v; + } + if (!query.getSelect().isEmpty()) { + for (Property select : query.getSelect()) { + v.visibleProperties.add(select); + if (select instanceof NavigationProperty) { + v.navLinkProperties.add((NavigationProperty) select); } } + } - for (Expand expand : query.getExpand()) { - List expPath = expand.getPath(); - Visibility level = v; - for (int i = 0; i < expPath.size(); i++) { - NavigationProperty np = expPath.get(i); - level.visibleProperties.add(np); - level.invisibleProperties.remove(np); - Visibility subLevel; - if (i == expPath.size() - 1) { - subLevel = createVisibility(np.type, expand.getSubQuery(), false); - } else { - subLevel = createVisibility(np.type, null, false); - } - Visibility existingVis = level.expandVisibility.get(np); - if (existingVis != null) { - subLevel.merge(existingVis); - } - level.expandVisibility.put(np, subLevel); - level = subLevel; - } - } + for (Expand expand : query.getExpand()) { + expandVisibility(v, expand); } return v; } - private static void copyEntityProperties(Set from, Set to) { - for (Property p : from) { - if (EntityProperty.class.isAssignableFrom(p.getClass())) { - to.add(p); + private static void expandVisibility(Visibility v, Expand expand) { + List expPath = expand.getPath(); + Visibility level = v; + for (int i = 0; i < expPath.size(); i++) { + NavigationProperty np = expPath.get(i); + Visibility subLevel; + if (i == expPath.size() - 1) { + subLevel = createVisibility(np.type, expand.getSubQuery(), false); + } else { + subLevel = createVisibility(np.type, null, false); + } + Visibility existingVis = level.expandVisibility.get(np); + if (existingVis != null) { + subLevel.merge(existingVis); } + level.expandVisibility.put(np, subLevel); + level = subLevel; } } - private static void copyNavigationProperties(Set from, Set to) { + private static void copyEntityProperties(Set from, Set to) { for (Property p : from) { - if (NavigationProperty.class.isAssignableFrom(p.getClass())) { - to.add((NavigationProperty) p); + if (EntityProperty.class.isAssignableFrom(p.getClass())) { + to.add(p); } } } - private static void copyNavigationProperties2(Set from, Set to) { + private static void copyNavigationProperties(Set from, Set to) { for (Property p : from) { if (NavigationProperty.class.isAssignableFrom(p.getClass())) { to.add((NavigationProperty) p); diff --git a/FROST-Server.Core/src/main/jjtree/staParser/queryParser.jjt b/FROST-Server.Core/src/main/jjtree/staParser/queryParser.jjt index c33651b0d..56f16036b 100644 --- a/FROST-Server.Core/src/main/jjtree/staParser/queryParser.jjt +++ b/FROST-Server.Core/src/main/jjtree/staParser/queryParser.jjt @@ -21,6 +21,7 @@ options { VISITOR = true; NODE_DEFAULT_VOID = true; STATIC = false; + UNICODE_INPUT = true; } PARSER_BEGIN(Parser) diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/deserialize/EntityParserTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/deserialize/EntityParserTest.java index cf71c860b..aeebd5897 100644 --- a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/deserialize/EntityParserTest.java +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/deserialize/EntityParserTest.java @@ -17,6 +17,7 @@ */ package de.fraunhofer.iosb.ilt.sta.deserialize; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayValue; import de.fraunhofer.iosb.ilt.sta.model.Datastream; @@ -40,8 +41,8 @@ import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; -import de.fraunhofer.iosb.ilt.sta.model.id.StringId; +import de.fraunhofer.iosb.ilt.sta.model.core.IdLong; +import de.fraunhofer.iosb.ilt.sta.model.core.IdString; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.util.TestHelper; import java.io.IOException; @@ -74,7 +75,7 @@ public class EntityParserTest { @Before public void setUp() { - entityParser = new EntityParser(LongId.class); + entityParser = new EntityParser(IdLong.class); } @After @@ -108,9 +109,9 @@ public void readDatastream_Basic_Success() throws IOException { .setObservationType("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement") .setName("Temperature measurement") .setDescription("Temperature measurement") - .setThing(new ThingBuilder().setId(new LongId(5394817)).build()) - .setObservedProperty(new ObservedPropertyBuilder().setId(new LongId(5394816)).build()) - .setSensor(new SensorBuilder().setId(new LongId(Long.MAX_VALUE)).build()) + .setThing(new ThingBuilder().setId(new IdLong(5394817)).build()) + .setObservedProperty(new ObservedPropertyBuilder().setId(new IdLong(5394816)).build()) + .setSensor(new SensorBuilder().setId(new IdLong(Long.MAX_VALUE)).build()) .build(); assertEquals(expectedResult, entityParser.parseDatastream(json)); } @@ -197,9 +198,9 @@ public void readDatastream_WithObservedAreaGeoJsonPolygon_Success() throws IOExc .setObservationType("http://www.opengis.net/def/observationType/OGC-OM/2.0/OM_Measurement") .setName("Temperature measurement") .setDescription("Temperature measurement") - .setThing(new ThingBuilder().setId(new LongId(5394817)).build()) - .setObservedProperty(new ObservedPropertyBuilder().setId(new LongId(5394816)).build()) - .setSensor(new SensorBuilder().setId(new LongId(5394815)).build()) + .setThing(new ThingBuilder().setId(new IdLong(5394817)).build()) + .setObservedProperty(new ObservedPropertyBuilder().setId(new IdLong(5394816)).build()) + .setSensor(new SensorBuilder().setId(new IdLong(5394815)).build()) .setObservedArea(TestHelper.getPolygon(2, 100, 0, 101, 0, 101, 1, 100, 1, 100, 0)) .build(); assertEquals(expectedResult, entityParser.parseDatastream(json)); @@ -465,8 +466,8 @@ public void readLocation_WithLinkedThings_Success() throws IOException { + " }," + " \"Things\":[{\"@iot.id\":100}]\n" + "}"; - Thing thing = new ThingBuilder().setId(new LongId(100)).build(); - EntitySet things = new EntitySetImpl<>(EntityType.Thing); + Thing thing = new ThingBuilder().setId(new IdLong(100)).build(); + EntitySet things = new EntitySetImpl<>(EntityType.THING); things.add(thing); Location expectedResult = new LocationBuilder() .setName("my backyard") @@ -530,7 +531,7 @@ public void readObservation_WithLinks_Success() throws IOException { .setPhenomenonTime(TimeInstant.create(new DateTime(2015, 04, 13, 0, 0, 0, DateTimeZone.UTC).getMillis())) .setResultTime(TimeInstant.create(new DateTime(2015, 04, 13, 0, 0, 05, DateTimeZone.UTC).getMillis())) .setResult(38) - .setDatastream(new DatastreamBuilder().setId(new LongId(100)).build()) + .setDatastream(new DatastreamBuilder().setId(new IdLong(100)).build()) .build(); assertEquals(expectedResult, entityParser.parseObservation(json)); @@ -544,7 +545,7 @@ public void readObservation_WithLinks_Success() throws IOException { .setPhenomenonTime(TimeInstant.create(new DateTime(2015, 04, 13, 0, 0, 0, DateTimeZone.UTC).getMillis())) .setResultTime(TimeInstant.create(new DateTime(2015, 04, 13, 0, 0, 05, DateTimeZone.UTC).getMillis())) .setResult(38) - .setMultiDatastream(new MultiDatastreamBuilder().setId(new LongId(100)).build()) + .setMultiDatastream(new MultiDatastreamBuilder().setId(new IdLong(100)).build()) .build(); assertEquals(expectedResult, entityParser.parseObservation(json)); } @@ -561,7 +562,7 @@ public void readObservation_WithLinkedFeatureOfInterest_Success() throws IOExcep .setPhenomenonTime(TimeInstant.create(new DateTime(2015, 04, 13, 0, 0, 0, DateTimeZone.UTC).getMillis())) .setResultTime(TimeInstant.create(new DateTime(2015, 04, 13, 0, 0, 05, DateTimeZone.UTC).getMillis())) .setResult(38) - .setFeatureOfInterest(new FeatureOfInterestBuilder().setId(new LongId(14269)).build()) + .setFeatureOfInterest(new FeatureOfInterestBuilder().setId(new IdLong(14269)).build()) .build(); assertEquals(expectedResult, entityParser.parseObservation(json)); } @@ -591,7 +592,7 @@ public void readObservation_WithFeatureOfInterest_Success() throws IOException { .setEncodingType("http://example.org/measurement_types#Measure") .setFeature("tarmac temperature") .build()) - .setDatastream(new DatastreamBuilder().setId(new LongId(14314)).build()) + .setDatastream(new DatastreamBuilder().setId(new IdLong(14314)).build()) .build(); assertEquals(expectedResult, entityParser.parseObservation(json)); } @@ -688,19 +689,19 @@ public void readObservation_DataArray() throws IOException { components.add("result"); components.add("FeatureOfInterest/id"); - Datastream ds1 = new DatastreamBuilder().setId(new LongId(1L)).build(); + Datastream ds1 = new DatastreamBuilder().setId(new IdLong(1L)).build(); DataArrayValue dav1 = new DataArrayValue(ds1, components); dav1.getDataArray().add(Arrays.asList(new Object[]{"2010-12-23T10:20:00-0700", 20, 1})); dav1.getDataArray().add(Arrays.asList(new Object[]{"2010-12-23T10:21:00-0700", 30, 1})); - Datastream ds2 = new DatastreamBuilder().setId(new LongId(2L)).build(); + Datastream ds2 = new DatastreamBuilder().setId(new IdLong(2L)).build(); DataArrayValue dav2 = new DataArrayValue(ds2, components); dav2.getDataArray().add(Arrays.asList(new Object[]{"2010-12-23T10:20:00-0700", 65, 1})); dav2.getDataArray().add(Arrays.asList(new Object[]{"2010-12-23T10:21:00-0700", 60, 1})); - MultiDatastream mds1 = new MultiDatastreamBuilder().setId(new LongId(2L)).build(); + MultiDatastream mds1 = new MultiDatastreamBuilder().setId(new IdLong(2L)).build(); DataArrayValue dav3 = new DataArrayValue(mds1, components); dav3.getDataArray().add(Arrays.asList(new Object[]{"2010-12-23T10:20:00-0700", 65, 1})); @@ -743,7 +744,7 @@ public void readObservedProperty_WithLinks_Success() throws IOException { .setDescription("http://schema.org/description") .setDefinition("Calibration date: Jan 1, 2014") .addDatastream(new DatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseObservedProperty(json)); @@ -761,7 +762,7 @@ public void readObservedProperty_WithLinks_Success() throws IOException { .setDescription("http://schema.org/description") .setDefinition("Calibration date: Jan 1, 2014") .addMultiDatastream(new MultiDatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseObservedProperty(json)); @@ -782,10 +783,10 @@ public void readObservedProperty_WithLinks_Success() throws IOException { .setDescription("http://schema.org/description") .setDefinition("Calibration date: Jan 1, 2014") .addDatastream(new DatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .addMultiDatastream(new MultiDatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseObservedProperty(json)); @@ -847,7 +848,7 @@ public void readSensor_WithLinks_Success() throws IOException { .setEncodingType("http://schema.org/description") .setMetadata("Calibration date: Jan 1, 2014") .addDatastream(new DatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseSensor(json)); @@ -867,7 +868,7 @@ public void readSensor_WithLinks_Success() throws IOException { .setEncodingType("http://schema.org/description") .setMetadata("Calibration date: Jan 1, 2014") .addMultiDatastream(new MultiDatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseSensor(json)); @@ -890,10 +891,10 @@ public void readSensor_WithLinks_Success() throws IOException { .setEncodingType("http://schema.org/description") .setMetadata("Calibration date: Jan 1, 2014") .addDatastream(new DatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .addMultiDatastream(new MultiDatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseSensor(json)); @@ -1059,7 +1060,7 @@ public void readThing_WithLinks_Success() throws IOException { .addProperty("property2", "it glows in the dark") .addProperty("property3", "it repels insects") .addLocation(new LocationBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseThing(json)); @@ -1083,7 +1084,7 @@ public void readThing_WithLinks_Success() throws IOException { .addProperty("property2", "it glows in the dark") .addProperty("property3", "it repels insects") .addDatastream(new DatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseThing(json)); @@ -1107,7 +1108,7 @@ public void readThing_WithLinks_Success() throws IOException { .addProperty("property2", "it glows in the dark") .addProperty("property3", "it repels insects") .addMultiDatastream(new MultiDatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseThing(json)); @@ -1134,10 +1135,10 @@ public void readThing_WithLinks_Success() throws IOException { .addProperty("property2", "it glows in the dark") .addProperty("property3", "it repels insects") .addDatastream(new DatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .addMultiDatastream(new MultiDatastreamBuilder() - .setId(new LongId(100)) + .setId(new IdLong(100)) .build()) .build(); assertEquals(expectedResult, entityParser.parseThing(json)); @@ -1287,20 +1288,20 @@ public void readEntity_LongId() throws IOException { { long id = Long.MAX_VALUE; String json = "{\"@iot.id\": " + id + "}"; - Thing expectedResult = new ThingBuilder().setId(new LongId(id)).build(); + Thing expectedResult = new ThingBuilder().setId(new IdLong(id)).build(); assertEquals(expectedResult, entityParser.parseThing(json)); } { long id = Long.MIN_VALUE; String json = "{\"@iot.id\": " + id + "}"; - Thing expectedResult = new ThingBuilder().setId(new LongId(id)).build(); + Thing expectedResult = new ThingBuilder().setId(new IdLong(id)).build(); assertEquals(expectedResult, entityParser.parseThing(json)); } { String id = UUID.randomUUID().toString(); String json = "{\"@iot.id\": \"" + id + "\"}"; - Thing expectedResult = new ThingBuilder().setId(new StringId(id)).build(); - assertEquals(expectedResult, new EntityParser(StringId.class).parseThing(json)); + Thing expectedResult = new ThingBuilder().setId(new IdString(id)).build(); + assertEquals(expectedResult, new EntityParser(IdString.class).parseThing(json)); } } diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageSerialisationTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageSerialisationTest.java new file mode 100644 index 000000000..66a2c31db --- /dev/null +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/messagebus/MessageSerialisationTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 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.sta.messagebus; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; +import de.fraunhofer.iosb.ilt.sta.model.Location; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.builder.DatastreamBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.LocationBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.ObservationBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.IdLong; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.util.TestHelper; +import java.io.IOException; +import org.junit.After; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +/** + * + * @author jab + */ +public class MessageSerialisationTest { + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + private T setExports(T entity) { + for (NavigationProperty property : entity.getEntityType().getNavigationEntities()) { + Object parentObject = entity.getProperty(property); + if (parentObject instanceof Entity) { + Entity parentEntity = (Entity) parentObject; + parentEntity.setExportObject(true); + } + } + return entity; + } + + @Test + public void serialiseMessageSimpleThing() throws IOException { + EntityChangedMessage message = new EntityChangedMessage(); + Thing entity = new ThingBuilder() + .setId(new IdLong(123456)) + .setName("testThing") + .setDescription("A Thing for testing") + .build(); + message.setEntity(entity); + setExports(entity); + + ObjectMapper mapper = EntityFormatter.getObjectMapper(); + String serialisedMessage = mapper.writeValueAsString(message); + + EntityParser parser = new EntityParser(IdLong.class); + EntityChangedMessage deserialisedMessage = parser.parseObject(EntityChangedMessage.class, serialisedMessage); + + assertEquals(message, deserialisedMessage); + } + + @Test + public void serialiseMessageLocation() throws IOException { + EntityChangedMessage message = new EntityChangedMessage(); + Location entity = new LocationBuilder() + .setId(new IdLong(123456)) + .setName("testThing") + .setDescription("A Thing for testing") + .setEncodingType("application/geo+json") + .setLocation(TestHelper.getPoint(-117.123, 54.123)) + .build(); + message.setEntity(entity); + setExports(entity); + + ObjectMapper mapper = EntityFormatter.getObjectMapper(); + String serialisedMessage = mapper.writeValueAsString(message); + + EntityParser parser = new EntityParser(IdLong.class); + EntityChangedMessage deserialisedMessage = parser.parseObject(EntityChangedMessage.class, serialisedMessage); + + assertEquals(message, deserialisedMessage); + } + + @Test + public void serialiseMessageThingWithFields() throws IOException { + EntityChangedMessage message = new EntityChangedMessage(); + Thing entity = new ThingBuilder() + .setId(new IdLong(123456)) + .setName("testThing") + .setDescription("A Thing for testing") + .build(); + message.setEntity(entity); + message.addEpField(EntityProperty.NAME); + message.addEpField(EntityProperty.DESCRIPTION); + message.addField(NavigationProperty.DATASTREAMS); + setExports(entity); + + ObjectMapper mapper = EntityFormatter.getObjectMapper(); + String serialisedMessage = mapper.writeValueAsString(message); + + EntityParser parser = new EntityParser(IdLong.class); + EntityChangedMessage deserialisedMessage = parser.parseObject(EntityChangedMessage.class, serialisedMessage); + + assertEquals(message, deserialisedMessage); + } + + @Test + public void serialiseMessageSimpleObservation() throws IOException { + EntityChangedMessage message = new EntityChangedMessage(); + Observation entity = new ObservationBuilder() + .setId(new IdLong(123456)) + .setResult(12345) + .addParameter("param1", "value 1") + .setDatastream(new DatastreamBuilder().setId(new IdLong(12)).build()) + .build(); + entity.setResultTime(new TimeInstant(null)); + message.setEntity(entity); + setExports(entity); + + ObjectMapper mapper = EntityFormatter.getObjectMapper(); + String serialisedMessage = mapper.writeValueAsString(message); + + EntityParser parser = new EntityParser(IdLong.class); + EntityChangedMessage deserialisedMessage = parser.parseObject(EntityChangedMessage.class, serialisedMessage); + + assertEquals(message, deserialisedMessage); + } + +} diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/model/EntityBuilderTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/model/EntityBuilderTest.java new file mode 100644 index 000000000..5fc08899d --- /dev/null +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/model/EntityBuilderTest.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2016 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.sta.model; + +import de.fraunhofer.iosb.ilt.sta.model.builder.DatastreamBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.FeatureOfInterestBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.HistoricalLocationBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.LocationBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.MultiDatastreamBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.ObservationBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.ObservedPropertyBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.SensorBuilder; +import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; +import de.fraunhofer.iosb.ilt.sta.model.core.IdLong; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.geojson.LngLatAlt; +import org.geojson.Point; +import org.geojson.Polygon; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author scf + */ +public class EntityBuilderTest { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(EntityBuilderTest.class); + private Map propertyValues = new HashMap<>(); + private Map propertyValuesAlternative = new HashMap<>(); + private Set equalsIgnores = new HashSet<>(); + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Before + public void setUp() { + propertyValues.put(EntityProperty.DEFINITION, "MyDefinition"); + propertyValues.put(EntityProperty.DESCRIPTION, "My description"); + propertyValues.put(EntityProperty.ENCODINGTYPE, "My EncodingType"); + propertyValues.put(EntityProperty.FEATURE, new Point(8, 42)); + propertyValues.put(EntityProperty.ID, new IdLong(1)); + propertyValues.put(EntityProperty.LOCATION, new Point(9, 43)); + propertyValues.put(EntityProperty.METADATA, "my meta data"); + propertyValues.put(EntityProperty.MULTIOBSERVATIONDATATYPES, Arrays.asList("Type 1", "Type 2")); + propertyValues.put(EntityProperty.NAME, "myName"); + propertyValues.put(EntityProperty.OBSERVATIONTYPE, "my Type"); + propertyValues.put(EntityProperty.OBSERVEDAREA, new Polygon(new LngLatAlt(0, 0), new LngLatAlt(1, 0), new LngLatAlt(1, 1))); + Map parameters = new HashMap<>(); + parameters.put("key1", "value1"); + parameters.put("key2", 2); + propertyValues.put(EntityProperty.PARAMETERS, parameters); + propertyValues.put(EntityProperty.PHENOMENONTIME, TimeInstant.now()); + propertyValuesAlternative.put(EntityProperty.PHENOMENONTIME, TimeInterval.parse("2014-03-02T13:00:00Z/2014-05-11T15:30:00Z")); + propertyValues.put(EntityProperty.PROPERTIES, parameters); + propertyValues.put(EntityProperty.RESULT, 42); + propertyValues.put(EntityProperty.RESULTQUALITY, "myQuality"); + propertyValues.put(EntityProperty.RESULTTIME, TimeInstant.now()); + propertyValuesAlternative.put(EntityProperty.RESULTTIME, TimeInterval.parse("2014-03-01T13:00:00Z/2014-05-11T15:30:00Z")); + propertyValues.put(EntityProperty.SELFLINK, "http://my.self/link"); + propertyValues.put(EntityProperty.TIME, TimeInstant.now()); + UnitOfMeasurement unit1 = new UnitOfMeasurement("unitName", "unitSymbol", "unitDefinition"); + UnitOfMeasurement unit2 = new UnitOfMeasurement("unitName2", "unitSymbol2", "unitDefinition2"); + propertyValues.put(EntityProperty.UNITOFMEASUREMENT, unit1); + propertyValues.put(EntityProperty.UNITOFMEASUREMENTS, Arrays.asList(unit1, unit2)); + propertyValues.put(EntityProperty.VALIDTIME, TimeInterval.parse("2014-03-01T13:00:00Z/2015-05-11T15:30:00Z")); + + for (EntityProperty ep : EntityProperty.values()) { + Assert.assertTrue("Missing value for " + ep, propertyValues.containsKey(ep)); + } + + int nextId = 100; + propertyValues.put(NavigationProperty.DATASTREAM, new Datastream(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.FEATUREOFINTEREST, new FeatureOfInterest(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.LOCATION, new Location(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.MULTIDATASTREAM, new MultiDatastream(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.OBSERVEDPROPERTY, new ObservedProperty(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.SENSOR, new Sensor(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.THING, new Thing(new IdLong(nextId++))); + + EntitySetImpl datastreams = new EntitySetImpl<>(EntityType.DATASTREAM); + datastreams.add(new Datastream(new IdLong(nextId++))); + datastreams.add(new Datastream(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.DATASTREAMS, datastreams); + + EntitySetImpl histLocations = new EntitySetImpl<>(EntityType.HISTORICALLOCATION); + histLocations.add(new HistoricalLocation(new IdLong(nextId++))); + histLocations.add(new HistoricalLocation(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.HISTORICALLOCATIONS, histLocations); + + EntitySetImpl locations = new EntitySetImpl<>(EntityType.LOCATION); + locations.add(new Location(new IdLong(nextId++))); + locations.add(new Location(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.LOCATIONS, locations); + + EntitySetImpl multiDatastreams = new EntitySetImpl<>(EntityType.MULTIDATASTREAM); + multiDatastreams.add(new MultiDatastream(new IdLong(nextId++))); + multiDatastreams.add(new MultiDatastream(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.MULTIDATASTREAMS, multiDatastreams); + + EntitySetImpl observations = new EntitySetImpl<>(EntityType.OBSERVATION); + observations.add(new Observation(new IdLong(nextId++))); + observations.add(new Observation(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.OBSERVATIONS, observations); + + EntitySetImpl obsProperties = new EntitySetImpl<>(EntityType.OBSERVEDPROPERTY); + obsProperties.add(new ObservedProperty(new IdLong(nextId++))); + obsProperties.add(new ObservedProperty(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.OBSERVEDPROPERTIES, obsProperties); + + EntitySetImpl things = new EntitySetImpl<>(EntityType.THING); + things.add(new Thing(new IdLong(nextId++))); + things.add(new Thing(new IdLong(nextId++))); + propertyValues.put(NavigationProperty.THINGS, things); + + for (NavigationProperty np : NavigationProperty.values()) { + Assert.assertTrue("Missing value for " + np, propertyValues.containsKey(np)); + } + + } + + @After + public void tearDown() { + } + + @Test + public void testEntityBuilders() throws ClassNotFoundException, InstantiationException, IllegalAccessException { + for (EntityType type : EntityType.values()) { + testEntityType(type, type.getPropertySet()); + } + } + + private void testEntityType(EntityType type, Set collectedProperties) { + try { + Class typeClass = type.getImplementingClass(); + String builderName = "de.fraunhofer.iosb.ilt.sta.model.builder." + typeClass.getSimpleName() + "Builder"; + Class builderClass = getClass().getClassLoader().loadClass(builderName); + + Entity entity = typeClass.newInstance(); + Object builder = builderClass.newInstance(); + for (Property p : collectedProperties) { + addPropertyToObject(entity, p); + if (equalsIgnores.contains(p)) { + Assert.assertEquals("Property " + p + " should not influence equals.", entity, buildBuilder(builder)); + } else { + Assert.assertNotEquals("Property " + p + " should influence equals.", entity, buildBuilder(builder)); + } + + addPropertyToObject(builder, p); + Entity buildEntity = (Entity) buildBuilder(builder); + Assert.assertEquals("Entities should be the same after adding " + p + " to both.", entity, buildEntity); + + getPropertyFromObject(entity, p); + getPropertyFromObject(buildEntity, p); + } + for (Property p : collectedProperties) { + + } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException ex) { + LOGGER.error("Failed to access property.", ex); + Assert.fail("Failed to access property: " + ex.getMessage()); + } + } + + private Object buildBuilder(Object builder) { + try { + return builder.getClass().getMethod("build").invoke(builder); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + Assert.fail("Failed to build: " + ex.getMessage()); + LOGGER.error("Failed to Build", ex); + } + return null; + } + + private void addPropertyToObject(Object entity, Property property) throws NoSuchMethodException { + try { + addPropertyToObject(entity, property, propertyValues); + } catch (NoSuchMethodException ex) { + addPropertyToObject(entity, property, propertyValuesAlternative); + } + } + + private void addPropertyToObject(Object entity, Property property, Map valuesToUse) throws NoSuchMethodException { + Object value = valuesToUse.get(property); + try { + final String setterName = property.getSetterName(); + MethodUtils.invokeMethod(entity, setterName, value); + } catch (NullPointerException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + LOGGER.error("Failed to set property " + property, ex); + Assert.fail("Failed to set property " + property + ": " + ex.getMessage()); + } + } + + private void getPropertyFromObject(Entity entity, Property property) { + try { + if (!(property instanceof NavigationProperty) && !entity.isSetProperty(property)) { + Assert.fail("Property " + property + " returned false for isSet on entity type " + entity.getEntityType()); + } + Object value = propertyValues.get(property); + Object value2 = propertyValuesAlternative.get(property); + Method getter = entity.getClass().getMethod(property.getGetterName()); + Object setValue = getter.invoke(entity); + + if (!(Objects.equals(value, setValue) || Objects.equals(value2, setValue))) { + Assert.fail("Getter did not return set value for property " + property + " on entity type " + entity.getEntityType()); + } + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + LOGGER.error("Failed to set property", ex); + Assert.fail("Failed to set property: " + ex.getMessage()); + } + } + + @Test + public void testDatastream() { + Datastream expected = new Datastream(); + Datastream builder = new DatastreamBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new Datastream(myId); + builder = new DatastreamBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + + expected = new Datastream(false, myId); + builder = new DatastreamBuilder().setId(myId).setUnitOfMeasurement(new UnitOfMeasurement()).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testFeatureOfInterest() { + FeatureOfInterest expected = new FeatureOfInterest(); + FeatureOfInterest builder = new FeatureOfInterestBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new FeatureOfInterest(myId); + builder = new FeatureOfInterestBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testHistoricalLocation() { + HistoricalLocation expected = new HistoricalLocation(); + HistoricalLocation builder = new HistoricalLocationBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new HistoricalLocation(myId); + builder = new HistoricalLocationBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testLocation() { + Location expected = new Location(); + Location builder = new LocationBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new Location(myId); + builder = new LocationBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testMultiDatastream() { + MultiDatastream expected = new MultiDatastream(); + MultiDatastream builder = new MultiDatastreamBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new MultiDatastream(myId); + builder = new MultiDatastreamBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testObservation() { + Observation expected = new Observation(); + Observation builder = new ObservationBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new Observation(myId); + builder = new ObservationBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testObservedProperty() { + ObservedProperty expected = new ObservedProperty(); + ObservedProperty builder = new ObservedPropertyBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new ObservedProperty(myId); + builder = new ObservedPropertyBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testSensor() { + Sensor expected = new Sensor(); + Sensor builder = new SensorBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new Sensor(myId); + builder = new SensorBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } + + @Test + public void testThing() { + Thing expected = new Thing(); + Thing builder = new ThingBuilder().build(); + Assert.assertEquals(expected, builder); + + IdLong myId = new IdLong(1); + expected = new Thing(myId); + builder = new ThingBuilder().setId(myId).build(); + Assert.assertEquals(expected, builder); + } +} diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/model/EntityCompleteTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/model/EntityCompleteTest.java index 871319207..ea802c094 100644 --- a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/model/EntityCompleteTest.java +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/model/EntityCompleteTest.java @@ -17,7 +17,7 @@ */ package de.fraunhofer.iosb.ilt.sta.model; -import de.fraunhofer.iosb.ilt.sta.deserialize.EntityParser; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; import de.fraunhofer.iosb.ilt.sta.model.builder.DatastreamBuilder; import de.fraunhofer.iosb.ilt.sta.model.builder.MultiDatastreamBuilder; import de.fraunhofer.iosb.ilt.sta.model.builder.ObservedPropertyBuilder; @@ -28,7 +28,7 @@ import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; +import de.fraunhofer.iosb.ilt.sta.model.core.IdLong; import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; import de.fraunhofer.iosb.ilt.sta.path.EntityType; @@ -55,7 +55,7 @@ public class EntityCompleteTest { @Before public void setUp() { - entityParser = new EntityParser(LongId.class); + entityParser = new EntityParser(IdLong.class); } @After @@ -73,7 +73,7 @@ private boolean isEntityComplete(Entity entity, EntitySetPathElement containingS @Test public void testMultiDatastreamComplete() { - EntitySetPathElement containingSet = new EntitySetPathElement(EntityType.MultiDatastream, null); + EntitySetPathElement containingSet = new EntitySetPathElement(EntityType.MULTIDATASTREAM, null); MultiDatastream entity = new MultiDatastream(); Assert.assertFalse(isEntityComplete(entity, containingSet)); @@ -97,22 +97,22 @@ public void testMultiDatastreamComplete() { entity.setMultiObservationDataTypes(multiObservationDataTypes); Assert.assertFalse(isEntityComplete(entity, containingSet)); - entity.setThing(new ThingBuilder().setId(new LongId(1)).build()); + entity.setThing(new ThingBuilder().setId(new IdLong(1)).build()); Assert.assertFalse(isEntityComplete(entity, containingSet)); - entity.setSensor(new SensorBuilder().setId(new LongId(2)).build()); + entity.setSensor(new SensorBuilder().setId(new IdLong(2)).build()); Assert.assertFalse(isEntityComplete(entity, containingSet)); - EntitySet observedProperties = new EntitySetImpl<>(EntityType.ObservedProperty); - observedProperties.add(new ObservedPropertyBuilder().setId(new LongId(3)).build()); + EntitySet observedProperties = new EntitySetImpl<>(EntityType.OBSERVEDPROPERTY); + observedProperties.add(new ObservedPropertyBuilder().setId(new IdLong(3)).build()); entity.setObservedProperties(observedProperties); Assert.assertTrue(isEntityComplete(entity, containingSet)); entity.setThing(null); Assert.assertFalse(isEntityComplete(entity, containingSet)); - Assert.assertTrue(isEntityComplete(entity, new EntitySetPathElement(EntityType.MultiDatastream, new EntityPathElement(new LongId(2), EntityType.Thing, null)))); + Assert.assertTrue(isEntityComplete(entity, new EntitySetPathElement(EntityType.MULTIDATASTREAM, new EntityPathElement(new IdLong(2), EntityType.THING, null)))); - Assert.assertFalse(isEntityComplete(entity, new EntitySetPathElement(EntityType.Datastream, null))); + Assert.assertFalse(isEntityComplete(entity, new EntitySetPathElement(EntityType.DATASTREAM, null))); unitOfMeasurements.add(new UnitOfMeasurementBuilder().setName("temperature").setDefinition("SomeUrl").setSymbol("degC").build()); entity.setUnitOfMeasurements(unitOfMeasurements); @@ -122,37 +122,37 @@ public void testMultiDatastreamComplete() { entity.setMultiObservationDataTypes(multiObservationDataTypes); Assert.assertFalse(isEntityComplete(entity, containingSet)); - observedProperties.add(new ObservedPropertyBuilder().setId(new LongId(3)).build()); + observedProperties.add(new ObservedPropertyBuilder().setId(new IdLong(3)).build()); entity.setObservedProperties(observedProperties); Assert.assertTrue(isEntityComplete(entity, containingSet)); } @Test public void testObservationComplete() { - EntitySetPathElement containingSet = new EntitySetPathElement(EntityType.Observation, null); + EntitySetPathElement containingSet = new EntitySetPathElement(EntityType.OBSERVATION, null); Observation entity = new Observation(); Assert.assertFalse(isEntityComplete(entity, containingSet)); entity.setResult("result"); Assert.assertFalse(isEntityComplete(entity, containingSet)); - entity.setDatastream(new DatastreamBuilder().setId(new LongId(2)).build()); + entity.setDatastream(new DatastreamBuilder().setId(new IdLong(2)).build()); Assert.assertTrue(isEntityComplete(entity, containingSet)); - entity.setMultiDatastream(new MultiDatastreamBuilder().setId(new LongId(2)).build()); + entity.setMultiDatastream(new MultiDatastreamBuilder().setId(new IdLong(2)).build()); Assert.assertFalse(isEntityComplete(entity, containingSet)); entity.setDatastream(null); Assert.assertTrue(isEntityComplete(entity, containingSet)); - Assert.assertFalse(isEntityComplete(entity, new EntitySetPathElement(EntityType.Datastream, null))); + Assert.assertFalse(isEntityComplete(entity, new EntitySetPathElement(EntityType.DATASTREAM, null))); - containingSet = new EntitySetPathElement(EntityType.Observation, new EntityPathElement(new LongId(1), EntityType.Datastream, null)); + containingSet = new EntitySetPathElement(EntityType.OBSERVATION, new EntityPathElement(new IdLong(1), EntityType.DATASTREAM, null)); entity = new Observation(); entity.setResult("result"); Assert.assertTrue(isEntityComplete(entity, containingSet)); - containingSet = new EntitySetPathElement(EntityType.Observation, new EntityPathElement(new LongId(1), EntityType.MultiDatastream, null)); + containingSet = new EntitySetPathElement(EntityType.OBSERVATION, new EntityPathElement(new IdLong(1), EntityType.MULTIDATASTREAM, null)); entity = new Observation(); entity.setResult("result"); Assert.assertTrue(isEntityComplete(entity, containingSet)); diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/PathParserTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/PathParserTest.java index 08094fdbd..956183bde 100644 --- a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/PathParserTest.java +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/PathParserTest.java @@ -17,9 +17,8 @@ */ package de.fraunhofer.iosb.ilt.sta.parser; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; -import de.fraunhofer.iosb.ilt.sta.model.id.StringId; +import de.fraunhofer.iosb.ilt.sta.model.core.IdLong; +import de.fraunhofer.iosb.ilt.sta.model.core.IdString; import de.fraunhofer.iosb.ilt.sta.parser.path.PathParser; import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyArrayIndex; import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; @@ -30,9 +29,11 @@ import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManagerString; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManagerlong; import org.junit.After; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -65,7 +66,7 @@ public void testParsePath_setThings() { ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, true, false); expResult.setMainElement(espe); @@ -78,22 +79,22 @@ public void testParsePath_setThingsRef() { ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, true, false); expResult.setMainElement(espe); expResult.setRef(true); - assert (result.equals(expResult)); + Assert.assertEquals(expResult, result); } private void testThing(long id) { String path = "/Things(" + id + ")"; - ResourcePath result = PathParser.parsePath(IdManager.ID_MANAGER_LONG, "", path); + ResourcePath result = PathParser.parsePath(new IdManagerlong(), "", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(id), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(id), EntityType.THING, espe); expResult.addPathElement(epe, true, true); assert (result.equals(expResult)); @@ -101,12 +102,12 @@ private void testThing(long id) { private void testThing(String id) { String path = "/Things('" + id + "')"; - ResourcePath result = PathParser.parsePath(IdManager.ID_MANAGER_STRING, "", path); + ResourcePath result = PathParser.parsePath(new IdManagerString(), "", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new StringId(id), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdString(id), EntityType.THING, espe); expResult.addPathElement(epe, true, true); assert (result.equals(expResult)); @@ -134,7 +135,7 @@ public void testParsePath_entityProperty() { ResourcePath expResult = new ResourcePath("", path); EntitySetPathElement espe = new EntitySetPathElement(entityType, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), entityType, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), entityType, espe); expResult.addPathElement(epe, true, true); PropertyPathElement ppe = new PropertyPathElement(entityProperty, epe); expResult.addPathElement(ppe, false, false); @@ -151,11 +152,11 @@ public void testParsePath_entityThingPropertyValue() { ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); expResult.setValue(true); @@ -168,11 +169,11 @@ public void testParsePath_entityThingSubProperty() { String path = "/Things(1)/properties/property1"; ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -182,11 +183,11 @@ public void testParsePath_entityThingSubProperty() { String path = "/Things(1)/properties/name_two"; ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("name_two", ppe); expResult.addPathElement(cppe, false, false); @@ -196,11 +197,11 @@ public void testParsePath_entityThingSubProperty() { String path = "/Things(1)/properties/property1[2]"; ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -212,11 +213,11 @@ public void testParsePath_entityThingSubProperty() { String path = "/Things(1)/properties/property1[2][3]"; ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -230,11 +231,11 @@ public void testParsePath_entityThingSubProperty() { String path = "/Things(1)/properties/property1[2]/deep[3]"; ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -254,11 +255,11 @@ public void testParsePath_entityObservation() { ResourcePath result = PathParser.parsePath("", path); ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Observation, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.OBSERVATION, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Observation, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Parameters, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PARAMETERS, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -267,11 +268,11 @@ public void testParsePath_entityObservation() { path = "/Observations(1)/parameters/property1[2]"; result = PathParser.parsePath("", path); expResult = new ResourcePath("", path); - espe = new EntitySetPathElement(EntityType.Observation, null); + espe = new EntitySetPathElement(EntityType.OBSERVATION, null); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(1), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(1), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, true, true); - ppe = new PropertyPathElement(EntityProperty.Parameters, epe); + ppe = new PropertyPathElement(EntityProperty.PARAMETERS, epe); expResult.addPathElement(ppe, false, false); cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -282,11 +283,11 @@ public void testParsePath_entityObservation() { path = "/Observations(1)/parameters/property1[2][3]"; result = PathParser.parsePath("", path); expResult = new ResourcePath("", path); - espe = new EntitySetPathElement(EntityType.Observation, null); + espe = new EntitySetPathElement(EntityType.OBSERVATION, null); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(1), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(1), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, true, true); - ppe = new PropertyPathElement(EntityProperty.Parameters, epe); + ppe = new PropertyPathElement(EntityProperty.PARAMETERS, epe); expResult.addPathElement(ppe, false, false); cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -299,11 +300,11 @@ public void testParsePath_entityObservation() { path = "/Observations(1)/parameters/property1[2]/deep[3]"; result = PathParser.parsePath("", path); expResult = new ResourcePath("", path); - espe = new EntitySetPathElement(EntityType.Observation, null); + espe = new EntitySetPathElement(EntityType.OBSERVATION, null); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(1), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(1), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, true, true); - ppe = new PropertyPathElement(EntityProperty.Parameters, epe); + ppe = new PropertyPathElement(EntityProperty.PARAMETERS, epe); expResult.addPathElement(ppe, false, false); cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -319,11 +320,11 @@ public void testParsePath_entityObservation() { result = PathParser.parsePath("", path); expResult = new ResourcePath("", path); - espe = new EntitySetPathElement(EntityType.Observation, null); + espe = new EntitySetPathElement(EntityType.OBSERVATION, null); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(1), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(1), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, true, true); - ppe = new PropertyPathElement(EntityProperty.Result, epe); + ppe = new PropertyPathElement(EntityProperty.RESULT, epe); expResult.addPathElement(ppe, false, false); cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -332,11 +333,11 @@ public void testParsePath_entityObservation() { path = "/Observations(1)/result[2]"; result = PathParser.parsePath("", path); expResult = new ResourcePath("", path); - espe = new EntitySetPathElement(EntityType.Observation, null); + espe = new EntitySetPathElement(EntityType.OBSERVATION, null); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(1), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(1), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, true, true); - ppe = new PropertyPathElement(EntityProperty.Result, epe); + ppe = new PropertyPathElement(EntityProperty.RESULT, epe); expResult.addPathElement(ppe, false, false); cpai = new CustomPropertyArrayIndex(2, ppe); expResult.addPathElement(cpai, false, false); @@ -351,51 +352,51 @@ public void testParsePath_deep1() { ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Location, epe); + espe = new EntitySetPathElement(EntityType.LOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(2), EntityType.Location, espe); + epe = new EntityPathElement(new IdLong(2), EntityType.LOCATION, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.HistoricalLocation, epe); + espe = new EntitySetPathElement(EntityType.HISTORICALLOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(3), EntityType.HistoricalLocation, espe); + epe = new EntityPathElement(new IdLong(3), EntityType.HISTORICALLOCATION, espe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.Thing, epe); + epe = new EntityPathElement(null, EntityType.THING, epe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Datastream, epe); + espe = new EntitySetPathElement(EntityType.DATASTREAM, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(5), EntityType.Datastream, espe); + epe = new EntityPathElement(new IdLong(5), EntityType.DATASTREAM, espe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.Sensor, epe); + epe = new EntityPathElement(null, EntityType.SENSOR, epe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Datastream, epe); + espe = new EntitySetPathElement(EntityType.DATASTREAM, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(6), EntityType.Datastream, espe); + epe = new EntityPathElement(new IdLong(6), EntityType.DATASTREAM, espe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.ObservedProperty, epe); + epe = new EntityPathElement(null, EntityType.OBSERVEDPROPERTY, epe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Datastream, epe); + espe = new EntitySetPathElement(EntityType.DATASTREAM, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(7), EntityType.Datastream, espe); + epe = new EntityPathElement(new IdLong(7), EntityType.DATASTREAM, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Observation, epe); + espe = new EntitySetPathElement(EntityType.OBSERVATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(8), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(8), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, false, true); - epe = new EntityPathElement(null, EntityType.FeatureOfInterest, epe); + epe = new EntityPathElement(null, EntityType.FEATUREOFINTEREST, epe); expResult.addPathElement(epe, true, false); assert (result.equals(expResult)); @@ -408,37 +409,37 @@ public void testParsePath_deep2() { ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.FeatureOfInterest, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.FEATUREOFINTEREST, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.FeatureOfInterest, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.FEATUREOFINTEREST, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Observation, epe); + espe = new EntitySetPathElement(EntityType.OBSERVATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(2), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(2), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.Datastream, epe); + epe = new EntityPathElement(null, EntityType.DATASTREAM, epe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.Thing, epe); + epe = new EntityPathElement(null, EntityType.THING, epe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.HistoricalLocation, epe); + espe = new EntitySetPathElement(EntityType.HISTORICALLOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(3), EntityType.HistoricalLocation, espe); + epe = new EntityPathElement(new IdLong(3), EntityType.HISTORICALLOCATION, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Location, epe); + espe = new EntitySetPathElement(EntityType.LOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(4), EntityType.Location, espe); + epe = new EntityPathElement(new IdLong(4), EntityType.LOCATION, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Thing, epe); + espe = new EntitySetPathElement(EntityType.THING, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); @@ -453,53 +454,53 @@ public void testParsePath_deep3() { ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.Thing, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.THING, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Location, epe); + espe = new EntitySetPathElement(EntityType.LOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(2), EntityType.Location, espe); + epe = new EntityPathElement(new IdLong(2), EntityType.LOCATION, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.HistoricalLocation, epe); + espe = new EntitySetPathElement(EntityType.HISTORICALLOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(3), EntityType.HistoricalLocation, espe); + epe = new EntityPathElement(new IdLong(3), EntityType.HISTORICALLOCATION, espe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.Thing, epe); + epe = new EntityPathElement(null, EntityType.THING, epe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.MultiDatastream, epe); + espe = new EntitySetPathElement(EntityType.MULTIDATASTREAM, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(5), EntityType.MultiDatastream, espe); + epe = new EntityPathElement(new IdLong(5), EntityType.MULTIDATASTREAM, espe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.Sensor, epe); + epe = new EntityPathElement(null, EntityType.SENSOR, epe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.MultiDatastream, epe); + espe = new EntitySetPathElement(EntityType.MULTIDATASTREAM, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(6), EntityType.MultiDatastream, espe); + epe = new EntityPathElement(new IdLong(6), EntityType.MULTIDATASTREAM, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.ObservedProperty, epe); + espe = new EntitySetPathElement(EntityType.OBSERVEDPROPERTY, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(7), EntityType.ObservedProperty, espe); + epe = new EntityPathElement(new IdLong(7), EntityType.OBSERVEDPROPERTY, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.MultiDatastream, epe); + espe = new EntitySetPathElement(EntityType.MULTIDATASTREAM, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(8), EntityType.MultiDatastream, espe); + epe = new EntityPathElement(new IdLong(8), EntityType.MULTIDATASTREAM, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Observation, epe); + espe = new EntitySetPathElement(EntityType.OBSERVATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(9), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(9), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, false, true); - epe = new EntityPathElement(null, EntityType.FeatureOfInterest, epe); + epe = new EntityPathElement(null, EntityType.FEATUREOFINTEREST, epe); expResult.addPathElement(epe, true, false); assert (result.equals(expResult)); @@ -512,37 +513,37 @@ public void testParsePath_deep4() { ResourcePath expResult = new ResourcePath("", path); - EntitySetPathElement espe = new EntitySetPathElement(EntityType.FeatureOfInterest, null); + EntitySetPathElement espe = new EntitySetPathElement(EntityType.FEATUREOFINTEREST, null); expResult.addPathElement(espe, false, false); - EntityPathElement epe = new EntityPathElement(new LongId(1), EntityType.FeatureOfInterest, espe); + EntityPathElement epe = new EntityPathElement(new IdLong(1), EntityType.FEATUREOFINTEREST, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Observation, epe); + espe = new EntitySetPathElement(EntityType.OBSERVATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(2), EntityType.Observation, espe); + epe = new EntityPathElement(new IdLong(2), EntityType.OBSERVATION, espe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.MultiDatastream, epe); + epe = new EntityPathElement(null, EntityType.MULTIDATASTREAM, epe); expResult.addPathElement(epe, false, false); - epe = new EntityPathElement(null, EntityType.Thing, epe); + epe = new EntityPathElement(null, EntityType.THING, epe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.HistoricalLocation, epe); + espe = new EntitySetPathElement(EntityType.HISTORICALLOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(3), EntityType.HistoricalLocation, espe); + epe = new EntityPathElement(new IdLong(3), EntityType.HISTORICALLOCATION, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Location, epe); + espe = new EntitySetPathElement(EntityType.LOCATION, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(4), EntityType.Location, espe); + epe = new EntityPathElement(new IdLong(4), EntityType.LOCATION, espe); expResult.addPathElement(epe, false, false); - espe = new EntitySetPathElement(EntityType.Thing, epe); + espe = new EntitySetPathElement(EntityType.THING, epe); expResult.addPathElement(espe, false, false); - epe = new EntityPathElement(new LongId(1), EntityType.Thing, espe); + epe = new EntityPathElement(new IdLong(1), EntityType.THING, espe); expResult.addPathElement(epe, true, true); - PropertyPathElement ppe = new PropertyPathElement(EntityProperty.Properties, epe); + PropertyPathElement ppe = new PropertyPathElement(EntityProperty.PROPERTIES, epe); expResult.addPathElement(ppe, false, false); CustomPropertyPathElement cppe = new CustomPropertyPathElement("property1", ppe); expResult.addPathElement(cppe, false, false); diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/QueryParserTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/QueryParserTest.java index 3452c08ee..a5ee78ada 100644 --- a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/QueryParserTest.java +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/parser/QueryParserTest.java @@ -44,12 +44,12 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.And; import de.fraunhofer.iosb.ilt.sta.query.expression.function.math.Round; import de.fraunhofer.iosb.ilt.sta.query.expression.function.temporal.Overlaps; +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import java.util.Arrays; +import java.util.HashSet; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; +import org.junit.Assert; import org.junit.Test; /** @@ -58,60 +58,62 @@ */ public class QueryParserTest { - public QueryParserTest() { - } - - @BeforeClass - public static void setUpClass() { - } - - @AfterClass - public static void tearDownClass() { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - @Test - public void testParseQuery_Top_Success() { - String query = "$top=10"; + public void testParseQuery_Top() { Query expResult = new Query(); + Assert.assertFalse(expResult.getTop().isPresent()); + Assert.assertEquals(CoreSettings.DEFAULT_MAX_TOP, expResult.getTopOrDefault()); expResult.setTop(10); + + String query = "$top=10"; Query result = QueryParser.parseQuery(query); - assert (result.equals(expResult)); + Assert.assertEquals(expResult, result); + Assert.assertTrue(result.getTop().isPresent()); + Assert.assertEquals(10, result.getTopOrDefault()); } @Test - public void testParseQuery_Skip_Success() { - String query = "$skip=10"; + public void testParseQuery_Skip() { Query expResult = new Query(); + Assert.assertFalse(expResult.getSkip().isPresent()); + Assert.assertEquals(11, expResult.getSkip(11)); expResult.setSkip(10); + + String query = "$skip=10"; Query result = QueryParser.parseQuery(query); - assert (result.equals(expResult)); + Assert.assertEquals(expResult, result); + Assert.assertTrue(result.getSkip().isPresent()); + Assert.assertEquals(10, result.getSkip(11)); } @Test - public void testParseQuery_Count_Success() { - String query = "$count=true"; + public void testParseQuery_Count() { Query expResult = new Query(); + Assert.assertFalse(expResult.getCount().isPresent()); + Assert.assertEquals(CoreSettings.DEFAULT_COUNT, expResult.isCountOrDefault()); + expResult.setCount(true); + String query = "$count=true"; Query result = QueryParser.parseQuery(query); - assert (result.equals(expResult)); + Assert.assertTrue(result.getCount().isPresent()); + Assert.assertTrue(result.isCountOrDefault()); + Assert.assertEquals(expResult, result); + + expResult.setCount(false); + query = "$count=false"; + result = QueryParser.parseQuery(query); + Assert.assertEquals(expResult, result); + Assert.assertFalse(result.isCountOrDefault()); } @Test - public void testParseQuery_FilterOnly1_Success() { + public void testParseQuery_FilterOnly1() { String query = "$filter=(result sub 5) gt 10"; Query expResult = new Query(); expResult.setFilter( new GreaterThan( new Subtract( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new IntegerConstant(5)), new IntegerConstant(10))); Query result = QueryParser.parseQuery(query); @@ -126,7 +128,7 @@ public void testParseQuery_FilterOnly1_Success() { new Divide( new IntegerConstant(14), new Add( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new IntegerConstant(1) ) ), @@ -142,12 +144,41 @@ public void testParseQuery_FilterOnly1_Success() { } @Test - public void testParseQuery_FilterString_Success() { + public void testParseQuery_FilterLinked() { + String query = "$filter=Datastream/id eq 1"; + Query expResult = new Query(); + expResult.setFilter( + new Equal( + new Path(NavigationProperty.DATASTREAM, EntityProperty.ID), + new IntegerConstant(1))); + Query result = QueryParser.parseQuery(query); + assert (result.equals(expResult)); + + // Theoretical path, does not actually exist + query = "$filter=Thing/Location/location eq 1"; + expResult = new Query(); + expResult.setFilter( + new Equal( + new Path(NavigationProperty.THING, NavigationProperty.LOCATION, EntityProperty.LOCATION), + new IntegerConstant(1))); + result = QueryParser.parseQuery(query); + assert (result.equals(expResult)); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseQuery_FilterInvalidCustomProperty() { + // Theoretical path, does not actually exist + String query = "$filter=Thing/custom eq 1"; + QueryParser.parseQuery(query); + } + + @Test + public void testParseQuery_FilterString() { String query = "$filter=result gt '3'"; Query expResult = new Query(); expResult.setFilter( new GreaterThan( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new StringConstant("3"))); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); @@ -156,7 +187,7 @@ public void testParseQuery_FilterString_Success() { expResult = new Query(); expResult.setFilter( new Equal( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new StringConstant("3"))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); @@ -165,7 +196,7 @@ public void testParseQuery_FilterString_Success() { expResult = new Query(); expResult.setFilter( new NotEqual( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new StringConstant("3"))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); @@ -174,7 +205,7 @@ public void testParseQuery_FilterString_Success() { expResult = new Query(); expResult.setFilter( new Equal( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new StringConstant("it's a quote"))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); @@ -183,10 +214,19 @@ public void testParseQuery_FilterString_Success() { expResult = new Query(); expResult.setFilter( new Equal( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new StringConstant("it''''s two quotes"))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); + + query = "$filter=description eq 'utf-8: 水位高度'"; + expResult = new Query(); + expResult.setFilter( + new Equal( + new Path(EntityProperty.DESCRIPTION), + new StringConstant("utf-8: 水位高度"))); + result = QueryParser.parseQuery(query); + assert (result.equals(expResult)); } @Test @@ -197,7 +237,7 @@ public void testParseQuery_FilterProperty() { expResult.setFilter( new GreaterThan( new Path( - EntityProperty.Properties, + EntityProperty.PROPERTIES, new CustomProperty("array"), new CustomProperty("[1]")), new IntegerConstant(3))); @@ -210,7 +250,7 @@ public void testParseQuery_FilterProperty() { expResult.setFilter( new GreaterThan( new Path( - EntityProperty.Properties, + EntityProperty.PROPERTIES, new CustomProperty("test_name")), new IntegerConstant(3))); Query result = QueryParser.parseQuery(query); @@ -222,7 +262,7 @@ public void testParseQuery_FilterProperty() { expResult.setFilter( new GreaterThan( new Path( - EntityProperty.Properties, + EntityProperty.PROPERTIES, new CustomProperty("array"), new CustomProperty("[1]"), new CustomProperty("[2]")), @@ -236,7 +276,7 @@ public void testParseQuery_FilterProperty() { expResult.setFilter( new GreaterThan( new Path( - EntityProperty.Properties, + EntityProperty.PROPERTIES, new CustomProperty("array"), new CustomProperty("[1]"), new CustomProperty("deeper"), @@ -251,7 +291,7 @@ public void testParseQuery_FilterProperty() { expResult.setFilter( new Equal( new Path( - EntityProperty.Location, + EntityProperty.LOCATION, new CustomProperty("properties"), new CustomProperty("priority")), new IntegerConstant(3))); @@ -261,12 +301,28 @@ public void testParseQuery_FilterProperty() { } @Test - public void testParseQuery_FilterTime_Success() { + public void testParseQuery_FilterUoM() { + { + String query = "$filter=unitOfMeasurement/name eq 'metre'"; + Query expResult = new Query(); + expResult.setFilter( + new Equal( + new Path( + EntityProperty.UNITOFMEASUREMENT, + new CustomProperty("name")), + new StringConstant("metre"))); + Query result = QueryParser.parseQuery(query); + assert (result.equals(expResult)); + } + } + + @Test + public void testParseQuery_FilterTime() { String query = "$filter=time gt 2015-10-14T23:30:00.104+02:00"; Query expResult = new Query(); expResult.setFilter( new GreaterThan( - new Path(EntityProperty.Time), + new Path(EntityProperty.TIME), new DateTimeConstant("2015-10-14T23:30:00.104+02:00"))); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); @@ -275,7 +331,7 @@ public void testParseQuery_FilterTime_Success() { expResult = new Query(); expResult.setFilter( new GreaterThan( - new Path(EntityProperty.Time), + new Path(EntityProperty.TIME), new Add( new DateTimeConstant("2015-10-14T23:30:00.104+02:00"), new DurationConstant("P1D") @@ -288,7 +344,7 @@ public void testParseQuery_FilterTime_Success() { expResult = new Query(); expResult.setFilter( new GreaterThan( - new Path(EntityProperty.Time), + new Path(EntityProperty.TIME), new IntervalConstant("2015-10-14T01:01:01.000+02:00/2015-10-14T23:30:00.104+02:00"))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); @@ -297,7 +353,7 @@ public void testParseQuery_FilterTime_Success() { expResult = new Query(); expResult.setFilter( new Overlaps( - new Path(EntityProperty.PhenomenonTime), + new Path(EntityProperty.PHENOMENONTIME), new IntervalConstant("2015-10-14T01:01:01.000+02:00/P1D"))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); @@ -306,21 +362,21 @@ public void testParseQuery_FilterTime_Success() { expResult = new Query(); expResult.setFilter( new Overlaps( - new Path(EntityProperty.PhenomenonTime), + new Path(EntityProperty.PHENOMENONTIME), new IntervalConstant("P1D/2015-10-14T01:01:01.000+02:00"))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_FilterFunction_Success() { + public void testParseQuery_FilterFunction() { String query = "$filter=round(result add 0.1) eq 2"; Query expResult = new Query(); expResult.setFilter( new Equal( new Round( new Add( - new Path(EntityProperty.Result), + new Path(EntityProperty.RESULT), new DoubleConstant(0.1) ) ), @@ -331,154 +387,169 @@ public void testParseQuery_FilterFunction_Success() { } @Test - public void testParseQuery_OrderByEntityProperty_Success() { + public void testParseQuery_OrderByEntityProperty() { String query = "$orderby=ID"; Query expResult = new Query(); - expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.Id))); + expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.ID))); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_OrderByAlias_Success() { + public void testParseQuery_OrderByAlias() { String query = "$orderby=@iot.id"; Query expResult = new Query(); - expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.Id))); + expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.ID))); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_OrderByAliasAscDesc_Success() { + public void testParseQuery_OrderByAliasAscDesc() { String query = "$orderby=@iot.id asc,@iot.id desc"; Query expResult = new Query(); - expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.Id))); - expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.Id), OrderBy.OrderType.Descending)); + expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.ID))); + expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.ID), OrderBy.OrderType.DESCENDING)); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_OrderByMixedPath_Success() { + public void testParseQuery_OrderByMixedPath() { String query = "$orderby=Datastream/@iot.id"; Query expResult = new Query(); - expResult.getOrderBy().add(new OrderBy(new Path(NavigationProperty.Datastream, EntityProperty.Id))); + expResult.getOrderBy().add(new OrderBy(new Path(NavigationProperty.DATASTREAM, EntityProperty.ID))); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); query = "$orderby=properties/subprop/name"; expResult = new Query(); - expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.Properties, new CustomProperty("subprop"), new CustomProperty("name")))); + expResult.getOrderBy().add(new OrderBy(new Path(EntityProperty.PROPERTIES, new CustomProperty("subprop"), new CustomProperty("name")))); result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_SelectEntityProperty_Success() { + public void testParseQuery_Select() { + Query expResult = new Query(); + expResult.getSelect().add(NavigationProperty.OBSERVATIONS); + expResult.getSelect().add(EntityProperty.ID); + Query result = new Query(); + result.setSelect(new HashSet<>(Arrays.asList(NavigationProperty.OBSERVATIONS, EntityProperty.ID))); + Assert.assertEquals(expResult, result); + + expResult.getSelect().clear(); + expResult.getSelect().add(NavigationProperty.THING); + expResult.getSelect().add(EntityProperty.ID); + result.setSelect(Arrays.asList(NavigationProperty.THING, EntityProperty.ID)); + Assert.assertEquals(expResult, result); + } + + @Test + public void testParseQuery_SelectEntityProperty() { String query = "$select=id"; Query expResult = new Query(); - expResult.getSelect().add(EntityProperty.Id); + expResult.getSelect().add(EntityProperty.ID); Query result = QueryParser.parseQuery(query); - assert (result.equals(expResult)); + Assert.assertEquals(expResult, result); } @Test - public void testParseQuery_SelectNavigationProperty_Success() { + public void testParseQuery_SelectNavigationProperty() { String query = "$select=Observations"; Query expResult = new Query(); - expResult.getSelect().add(NavigationProperty.Observations); + expResult.getSelect().add(NavigationProperty.OBSERVATIONS); Query result = QueryParser.parseQuery(query); - assert (result.equals(expResult)); + Assert.assertEquals(expResult, result); } @Test - public void testParseQuery_SelectMultipleMixed_Success() { + public void testParseQuery_SelectMultipleMixed() { String query = "$select=Observations, id"; Query expResult = new Query(); - expResult.getSelect().add(NavigationProperty.Observations); - expResult.getSelect().add(EntityProperty.Id); + expResult.setSelect(Arrays.asList(NavigationProperty.OBSERVATIONS, EntityProperty.ID)); Query result = QueryParser.parseQuery(query); - assert (result.equals(expResult)); + Assert.assertEquals(expResult, result); } @Test - public void testParseQuery_ExpandSingleNavigationProperty_Success() { + public void testParseQuery_ExpandSingleNavigationProperty() { String query = "$expand=Observations"; Query expResult = new Query(); - expResult.getExpand().add(new Expand(NavigationProperty.Observations)); + expResult.getExpand().add(new Expand(NavigationProperty.OBSERVATIONS)); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_ExpandMultipleNavigationPropertes_Success() { + public void testParseQuery_ExpandMultipleNavigationPropertes() { String query = "$expand=Observations,ObservedProperty"; Query expResult = new Query(); - expResult.getExpand().add(new Expand(NavigationProperty.Observations)); - expResult.getExpand().add(new Expand(NavigationProperty.ObservedProperty)); + expResult.getExpand().add(new Expand(NavigationProperty.OBSERVATIONS)); + expResult.getExpand().add(new Expand(NavigationProperty.OBSERVEDPROPERTY)); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_ExpandWithSubquery_Success() { + public void testParseQuery_ExpandWithSubquery() { String query = "$expand=Observations($filter=result eq 1;$expand=FeatureOfInterest;$select=@iot.id;$orderby=id;$skip=5;$top=10;$count=true),ObservedProperty&$top=10"; Query expResult = new Query(); Query subQuery = new Query(); - subQuery.setFilter(new Equal(new Path(EntityProperty.Result), new IntegerConstant(1))); - subQuery.getExpand().add(new Expand(NavigationProperty.FeatureOfInterest)); - subQuery.getSelect().add(EntityProperty.Id); - subQuery.getOrderBy().add(new OrderBy(new Path(EntityProperty.Id))); + subQuery.setFilter(new Equal(new Path(EntityProperty.RESULT), new IntegerConstant(1))); + subQuery.getExpand().add(new Expand(NavigationProperty.FEATUREOFINTEREST)); + subQuery.getSelect().add(EntityProperty.ID); + subQuery.getOrderBy().add(new OrderBy(new Path(EntityProperty.ID))); subQuery.setSkip(5); subQuery.setTop(10); subQuery.setCount(true); - expResult.getExpand().add(new Expand(subQuery, NavigationProperty.Observations)); - expResult.getExpand().add(new Expand(NavigationProperty.ObservedProperty)); + expResult.getExpand().add(new Expand(subQuery, NavigationProperty.OBSERVATIONS)); + expResult.getExpand().add(new Expand(NavigationProperty.OBSERVEDPROPERTY)); expResult.setTop(10); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_Complex1_Success() { + public void testParseQuery_Complex1() { String query = "$expand=Observations($filter=result eq 1;$expand=FeatureOfInterest;$select=@iot.id),ObservedProperty&$top=10"; Query expResult = new Query(); Query subQuery1 = new Query(); - subQuery1.setFilter(new Equal(new Path(EntityProperty.Result), new IntegerConstant(1))); - subQuery1.getExpand().add(new Expand(NavigationProperty.FeatureOfInterest)); - subQuery1.getSelect().add(EntityProperty.Id); - expResult.getExpand().add(new Expand(subQuery1, NavigationProperty.Observations)); - expResult.getExpand().add(new Expand(NavigationProperty.ObservedProperty)); + subQuery1.setFilter(new Equal(new Path(EntityProperty.RESULT), new IntegerConstant(1))); + subQuery1.getExpand().add(new Expand(NavigationProperty.FEATUREOFINTEREST)); + subQuery1.getSelect().add(EntityProperty.ID); + expResult.getExpand().add(new Expand(subQuery1, NavigationProperty.OBSERVATIONS)); + expResult.getExpand().add(new Expand(NavigationProperty.OBSERVEDPROPERTY)); expResult.setTop(10); Query result = QueryParser.parseQuery(query); assert (result.equals(expResult)); } @Test - public void testParseQuery_FilterComplex_Success() { + public void testParseQuery_FilterComplex() { String query = "$filter=Datastreams/Observations/FeatureOfInterest/id eq 'FOI_1' and Datastreams/Observations/resultTime ge 2010-06-01T00:00:00Z and Datastreams/Observations/resultTime le 2010-07-01T00:00:00Z"; Query expResult = new Query(); expResult.setFilter( new And( new Equal( - new Path(NavigationProperty.Datastreams, - NavigationProperty.Observations, - NavigationProperty.FeatureOfInterest, - EntityProperty.Id), + new Path(NavigationProperty.DATASTREAMS, + NavigationProperty.OBSERVATIONS, + NavigationProperty.FEATUREOFINTEREST, + EntityProperty.ID), new StringConstant("FOI_1")), new And( new GreaterEqual( - new Path(NavigationProperty.Datastreams, - NavigationProperty.Observations, - EntityProperty.ResultTime), + new Path(NavigationProperty.DATASTREAMS, + NavigationProperty.OBSERVATIONS, + EntityProperty.RESULTTIME), new DateTimeConstant(new DateTime(2010, 06, 01, 0, 0, DateTimeZone.UTC))), new LessEqual( - new Path(NavigationProperty.Datastreams, - NavigationProperty.Observations, - EntityProperty.ResultTime), + new Path(NavigationProperty.DATASTREAMS, + NavigationProperty.OBSERVATIONS, + EntityProperty.RESULTTIME), new DateTimeConstant(new DateTime(2010, 07, 01, 0, 0, DateTimeZone.UTC)))))); Query result = QueryParser.parseQuery(query); - assert (result.equals(expResult)); + Assert.assertEquals(expResult, result); } // TODO add tests for all functions diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/serialize/EntityFormatterTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/serialize/EntityFormatterTest.java index 46685c01c..5930cda99 100644 --- a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/serialize/EntityFormatterTest.java +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/serialize/EntityFormatterTest.java @@ -17,11 +17,12 @@ */ package de.fraunhofer.iosb.ilt.sta.serialize; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayResult; import de.fraunhofer.iosb.ilt.sta.formatter.DataArrayValue; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; +import de.fraunhofer.iosb.ilt.sta.json.serialize.EntityFormatter; import de.fraunhofer.iosb.ilt.sta.model.Datastream; import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; import de.fraunhofer.iosb.ilt.sta.model.Thing; @@ -38,15 +39,20 @@ import de.fraunhofer.iosb.ilt.sta.model.core.Entity; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; +import de.fraunhofer.iosb.ilt.sta.model.core.IdLong; import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; +import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.util.TestHelper; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.joda.time.DateTimeZone; @@ -90,17 +96,42 @@ public void writeThing_Basic_Success() throws IOException { + "}\n" + "}"; Thing entity = new ThingBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Things(1)") - .setLocations(new EntitySetImpl(EntityType.Location, "Things(1)/Locations")) - .setDatastreams(new EntitySetImpl(EntityType.Datastream, "Things(1)/Datastreams")) - .setHistoricalLocations(new EntitySetImpl(EntityType.Thing, "Things(1)/HistoricalLocations")) + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) + .setDatastreams(new EntitySetImpl(EntityType.DATASTREAM, "Things(1)/Datastreams")) + .setHistoricalLocations(new EntitySetImpl(EntityType.THING, "Things(1)/HistoricalLocations")) .setName("This thing is an oven.") .setDescription("This thing is an oven.") .addProperty("owner", "John Doe") .addProperty("color", "Silver") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); + } + + @Test + public void writeThing_Select_Success() throws IOException { + String expResult + = "{\n" + + "\"@iot.id\": 1,\n" + + "\"name\": \"This thing is an oven.\"\n" + + "}"; + Thing entity = new ThingBuilder() + .setId(new IdLong(1)) + .setSelfLink("http://example.org/v1.0/Things(1)") + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) + .setDatastreams(new EntitySetImpl(EntityType.DATASTREAM, "Things(1)/Datastreams")) + .setHistoricalLocations(new EntitySetImpl(EntityType.THING, "Things(1)/HistoricalLocations")) + .setName("This thing is an oven.") + .setDescription("This thing is an oven.") + .addProperty("owner", "John Doe") + .addProperty("color", "Silver") + .build(); + Set selectedProps = new HashSet<>(); + selectedProps.add(EntityProperty.ID); + selectedProps.add(EntityProperty.NAME); + entity.setSelectedProperties(selectedProps); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -125,20 +156,20 @@ public void writeThings_Basic_Success() throws IOException { + thing + "]}"; Thing entity = new ThingBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Things(1)") - .setLocations(new EntitySetImpl(EntityType.Location, "Things(1)/Locations")) - .setDatastreams(new EntitySetImpl(EntityType.Datastream, "Things(1)/Datastreams")) - .setHistoricalLocations(new EntitySetImpl(EntityType.HistoricalLocation, "Things(1)/HistoricalLocations")) + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) + .setDatastreams(new EntitySetImpl(EntityType.DATASTREAM, "Things(1)/Datastreams")) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Things(1)/HistoricalLocations")) .setName("This thing is an oven.") .setDescription("This thing is an oven.") .addProperty("owner", "John Doe") .addProperty("color", "Silver") .build(); - EntitySet things = new EntitySetImpl<>(EntityType.Thing); + EntitySet things = new EntitySetImpl<>(EntityType.THING); things.add(entity); things.add(entity); - assert (jsonEqual(expResult, new EntityFormatter().writeEntityCollection(things))); + assert (jsonEqual(expResult, EntityFormatter.writeEntityCollection(things))); } @Test @@ -147,7 +178,7 @@ public void writeThing_CompletelyEmpty_Success() throws IOException { = "{}"; Thing entity = new ThingBuilder() .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -193,11 +224,11 @@ public void writeThings_WithExpandedDatastream_Success() throws IOException { + thing + "]}"; Thing entity = new ThingBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Things(1)") - .setLocations(new EntitySetImpl(EntityType.Location, "Things(1)/Locations")) + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) .addDatastream(new DatastreamBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Datastreams(1)") .setName("This is a datastream measuring the temperature in an oven.") .setDescription("This is a datastream measuring the temperature in an oven.") @@ -211,17 +242,17 @@ public void writeThings_WithExpandedDatastream_Success() throws IOException { .setPhenomenonTime(TestHelper.createTimeInterval(2014, 03, 1, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .setResultTime(TestHelper.createTimeInterval(2014, 03, 01, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .build()) - .setHistoricalLocations(new EntitySetImpl(EntityType.HistoricalLocation, "Things(1)/HistoricalLocations")) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Things(1)/HistoricalLocations")) .setName("This thing is an oven.") .setDescription("This thing is an oven.") .addProperty("owner", "John Doe") .addProperty("color", "Silver") .build(); entity.getDatastreams().setCount(1); - EntitySet things = new EntitySetImpl<>(EntityType.Thing); + EntitySet things = new EntitySetImpl<>(EntityType.THING); things.add(entity); things.setCount(1); - assert (jsonEqual(expResult, new EntityFormatter().writeEntityCollection(things))); + assert (jsonEqual(expResult, EntityFormatter.writeEntityCollection(things))); } @Test @@ -241,19 +272,68 @@ public void writeThing_WithExpandedDatastream_Success() throws Exception { + "}\n" + "}"; Thing entity = new ThingBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) + .setSelfLink("http://example.org/v1.0/Things(1)") + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) + .addDatastream(new DatastreamBuilder() + .setId(new IdLong(123)) + .build()) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Things(1)/HistoricalLocations")) + .setName("This thing is an oven.") + .setDescription("This thing is an oven.") + .addProperty("owner", "John Doe") + .addProperty("color", "Silver") + .build(); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); + + expResult + = "{\n" + + "\"@iot.id\": 1,\n" + + "\"Locations@iot.navigationLink\": \"Things(1)/Locations\",\n" + + "\"Datastreams\": [{\"@iot.id\":123}],\n" + + "\"name\": \"This thing is an oven.\"\n" + + "}"; + entity = new ThingBuilder() + .setId(new IdLong(1)) + .setSelfLink("http://example.org/v1.0/Things(1)") + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) + .addDatastream(new DatastreamBuilder() + .setId(new IdLong(123)) + .build()) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Things(1)/HistoricalLocations")) + .setName("This thing is an oven.") + .setDescription("This thing is an oven.") + .addProperty("owner", "John Doe") + .addProperty("color", "Silver") + .build(); + entity.setSelectedPropertyNames(new HashSet<>(Arrays.asList(EntityProperty.ID.getJsonName(), "name", "Locations"))); + entity.getDatastreams().setExportObject(true); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); + + expResult + = "{\n" + + "\"@iot.selfLink\": \"http://example.org/v1.0/Things(1)\",\n" + + "\"Locations@iot.navigationLink\": \"Things(1)/Locations\",\n" + + "\"Datastreams\": [{\"@iot.id\":123}],\n" + + "\"name\": \"This thing is an oven.\"\n" + + "}"; + entity = new ThingBuilder() + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Things(1)") - .setLocations(new EntitySetImpl(EntityType.Location, "Things(1)/Locations")) + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) .addDatastream(new DatastreamBuilder() - .setId(new LongId(123)) + .setId(new IdLong(123)) .build()) - .setHistoricalLocations(new EntitySetImpl(EntityType.HistoricalLocation, "Things(1)/HistoricalLocations")) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Things(1)/HistoricalLocations")) .setName("This thing is an oven.") .setDescription("This thing is an oven.") .addProperty("owner", "John Doe") .addProperty("color", "Silver") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + entity.setSelectedPropertyNames(new HashSet<>(Arrays.asList(EntityProperty.SELFLINK.getJsonName(), "name", "Locations"))); + entity.getDatastreams().setExportObject(true); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); + expResult = "{\n" + " \"@iot.id\": 1,\n" @@ -269,17 +349,17 @@ public void writeThing_WithExpandedDatastream_Success() throws Exception { + " }\n" + "}"; entity = new ThingBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Things(1)") - .setLocations(new EntitySetImpl(EntityType.Location, "Things(1)/Locations")) - .setHistoricalLocations(new EntitySetImpl(EntityType.HistoricalLocation, "Things(1)/HistoricalLocations")) + .setLocations(new EntitySetImpl(EntityType.LOCATION, "Things(1)/Locations")) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Things(1)/HistoricalLocations")) .setName("This thing is an oven.") .setDescription("This thing is an oven.") .addProperty("owner", "John Doe") .addProperty("color", "Silver") .build(); entity.getDatastreams().setExportObject(true); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -294,13 +374,13 @@ public void writeLocation_Basic_Success() throws Exception { + " \"encodingType\": \"application/vnd.geo+json\"" + "}"; Entity entity = new LocationBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Locations(1)") - .setThings(new EntitySetImpl(EntityType.Thing, "Locations(1)/Things")) - .setHistoricalLocations(new EntitySetImpl(EntityType.HistoricalLocation, "Locations(1)/HistoricalLocations")) + .setThings(new EntitySetImpl(EntityType.THING, "Locations(1)/Things")) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Locations(1)/HistoricalLocations")) .setEncodingType("application/vnd.geo+json") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } { String expResult @@ -312,13 +392,13 @@ public void writeLocation_Basic_Success() throws Exception { + " \"encodingType\": \"application/geo+json\"" + "}"; Entity entity = new LocationBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Locations(1)") - .setThings(new EntitySetImpl(EntityType.Thing, "Locations(1)/Things")) - .setHistoricalLocations(new EntitySetImpl(EntityType.HistoricalLocation, "Locations(1)/HistoricalLocations")) + .setThings(new EntitySetImpl(EntityType.THING, "Locations(1)/Things")) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Locations(1)/HistoricalLocations")) .setEncodingType("application/geo+json") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } } @@ -343,14 +423,14 @@ public void writeLocation_WithGeoJsonLocation_Success() throws Exception { + " }\n" + "}"; Entity entity = new LocationBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Locations(1)") - .setThings(new EntitySetImpl(EntityType.Thing, "Locations(1)/Things")) - .setHistoricalLocations(new EntitySetImpl(EntityType.HistoricalLocation, "Locations(1)/HistoricalLocations")) + .setThings(new EntitySetImpl(EntityType.THING, "Locations(1)/Things")) + .setHistoricalLocations(new EntitySetImpl(EntityType.HISTORICALLOCATION, "Locations(1)/HistoricalLocations")) .setEncodingType("application/vnd.geo+json") .setLocation(TestHelper.getFeatureWithPoint(-114.06, 51.05)) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -364,13 +444,13 @@ public void writeHistoricalLocation_Basic_Success() throws Exception { + " \"time\": \"2015-01-25T19:00:00.000Z\"\n" + "}"; Entity entity = new HistoricalLocationBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/HistoricalLocations(1)") - .setLocations(new EntitySetImpl(EntityType.Location, "HistoricalLocations(1)/Locations")) + .setLocations(new EntitySetImpl(EntityType.LOCATION, "HistoricalLocations(1)/Locations")) .setThing(new ThingBuilder().setNavigationLink("HistoricalLocations(1)/Thing").build()) .setTime(TestHelper.createTimeInstant(2015, 01, 25, 12, 0, 0, DateTimeZone.forOffsetHours(-7), DateTimeZone.UTC)) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -396,12 +476,12 @@ public void writeDatastream_Basic_Success() throws Exception { + " \"resultTime\": \"2014-03-01T13:00:00.000Z/2015-05-11T15:30:00.000Z\"\n" + "}"; Entity entity = new DatastreamBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Datastreams(1)") .setThing(new ThingBuilder().setNavigationLink("HistoricalLocations(1)/Thing").build()) .setSensor(new SensorBuilder().setNavigationLink("Datastreams(1)/Sensor").build()) .setObservedProperty(new ObservedPropertyBuilder().setNavigationLink("Datastreams(1)/ObservedProperty").build()) - .setObservations(new EntitySetImpl(EntityType.Observation, "Datastreams(1)/Observations")) + .setObservations(new EntitySetImpl(EntityType.OBSERVATION, "Datastreams(1)/Observations")) .setName("This is a datastream measuring the temperature in an oven.") .setDescription("This is a datastream measuring the temperature in an oven.") .setUnitOfMeasurement(new UnitOfMeasurementBuilder() @@ -413,7 +493,7 @@ public void writeDatastream_Basic_Success() throws Exception { .setPhenomenonTime(TestHelper.createTimeInterval(2014, 03, 1, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .setResultTime(TestHelper.createTimeInterval(2014, 03, 01, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -439,19 +519,20 @@ public void writeDatastream_WithEmptyUnitOfMeasurement_Success() throws Exceptio + " \"resultTime\": \"2014-03-01T13:00:00.000Z/2015-05-11T15:30:00.000Z\"\n" + "}"; Entity entity = new DatastreamBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Datastreams(1)") .setThing(new ThingBuilder().setNavigationLink("HistoricalLocations(1)/Thing").build()) .setSensor(new SensorBuilder().setNavigationLink("Datastreams(1)/Sensor").build()) .setObservedProperty(new ObservedPropertyBuilder().setNavigationLink("Datastreams(1)/ObservedProperty").build()) - .setObservations(new EntitySetImpl(EntityType.Observation, "Datastreams(1)/Observations")) + .setObservations(new EntitySetImpl(EntityType.OBSERVATION, "Datastreams(1)/Observations")) + .setUnitOfMeasurement(new UnitOfMeasurement()) .setName("This is a datastream measuring the temperature in an oven.") .setDescription("This is a datastream measuring the temperature in an oven.") .setObservationType("http://www.opengis.net/def/observationType/OGCOM/2.0/OM_Measurement") .setPhenomenonTime(TestHelper.createTimeInterval(2014, 03, 1, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .setResultTime(TestHelper.createTimeInterval(2014, 03, 01, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -482,12 +563,12 @@ public void writeDatastream_WithObservedAreaGeoJsonPolygon_Success() throws Exce + " \"resultTime\": \"2014-03-01T13:00:00.000Z/2015-05-11T15:30:00.000Z\"\n" + "}"; Entity entity = new DatastreamBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Datastreams(1)") .setThing(new ThingBuilder().setNavigationLink("HistoricalLocations(1)/Thing").build()) .setSensor(new SensorBuilder().setNavigationLink("Datastreams(1)/Sensor").build()) .setObservedProperty(new ObservedPropertyBuilder().setNavigationLink("Datastreams(1)/ObservedProperty").build()) - .setObservations(new EntitySetImpl(EntityType.Observation, "Datastreams(1)/Observations")) + .setObservations(new EntitySetImpl(EntityType.OBSERVATION, "Datastreams(1)/Observations")) .setName("This is a datastream measuring the temperature in an oven.") .setDescription("This is a datastream measuring the temperature in an oven.") .setUnitOfMeasurement(new UnitOfMeasurementBuilder() @@ -500,7 +581,7 @@ public void writeDatastream_WithObservedAreaGeoJsonPolygon_Success() throws Exce .setPhenomenonTime(TestHelper.createTimeInterval(2014, 03, 1, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .setResultTime(TestHelper.createTimeInterval(2014, 03, 01, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -536,12 +617,12 @@ public void writeMultiDatastream_Basic_Success() throws Exception { + " \"resultTime\": \"2014-03-01T13:00:00.000Z/2015-05-11T15:30:00.000Z\"\n" + "}"; Entity entity = new MultiDatastreamBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/MultiDatastreams(1)") .setThing(new ThingBuilder().setNavigationLink("HistoricalLocations(1)/Thing").build()) .setSensor(new SensorBuilder().setNavigationLink("MultiDatastreams(1)/Sensor").build()) - .setObservations(new EntitySetImpl(EntityType.Observation, "MultiDatastreams(1)/Observations")) - .setObservedProperties(new EntitySetImpl(EntityType.ObservedProperty, "MultiDatastreams(1)/ObservedProperties")) + .setObservations(new EntitySetImpl(EntityType.OBSERVATION, "MultiDatastreams(1)/Observations")) + .setObservedProperties(new EntitySetImpl(EntityType.OBSERVEDPROPERTY, "MultiDatastreams(1)/ObservedProperties")) .setName("This is a datastream measuring the wind.") .setDescription("This is a datastream measuring wind direction and speed.") .addUnitOfMeasurement(new UnitOfMeasurementBuilder() @@ -559,7 +640,7 @@ public void writeMultiDatastream_Basic_Success() throws Exception { .setPhenomenonTime(TestHelper.createTimeInterval(2014, 03, 1, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .setResultTime(TestHelper.createTimeInterval(2014, 03, 01, 13, 0, 0, 2015, 05, 11, 15, 30, 0, DateTimeZone.UTC)) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -575,15 +656,15 @@ public void writeSensor_Basic_Success() throws Exception { + " \"metadata\": \"http://example.org/TMP35_36_37.pdf\"\n" + "}"; Entity entity = new SensorBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Sensors(1)") - .setDatastreams(new EntitySetImpl(EntityType.Datastream, "Sensors(1)/Datastreams")) + .setDatastreams(new EntitySetImpl(EntityType.DATASTREAM, "Sensors(1)/Datastreams")) .setName("TMP36 - Analog Temperature sensor") .setDescription("TMP36 - Analog Temperature sensor") .setEncodingType("application/pdf") .setMetadata("http://example.org/TMP35_36_37.pdf") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -598,15 +679,15 @@ public void writeSensor_EmptyDatastreamsCollection_Success() throws Exception { + " \"metadata\": \"http://example.org/TMP35_36_37.pdf\"\n" + "}"; Entity entity = new SensorBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Sensors(1)") - .setDatastreams(new EntitySetImpl(EntityType.Datastream)) + .setDatastreams(new EntitySetImpl(EntityType.DATASTREAM)) .setName("TMP36 - Analog Temperature sensor") .setDescription("TMP36 - Analog Temperature sensor") .setEncodingType("application/pdf") .setMetadata("http://example.org/TMP35_36_37.pdf") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -621,14 +702,14 @@ public void writeObservedPropertyBasic_Success() throws Exception { + " \"definition\": \"http://dbpedia.org/page/Dew_point\"\n" + "}"; Entity entity = new ObservedPropertyBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/ObservedProperties(1)") - .setDatastreams(new EntitySetImpl(EntityType.Datastream, "ObservedProperties(1)/Datastreams")) + .setDatastreams(new EntitySetImpl(EntityType.DATASTREAM, "ObservedProperties(1)/Datastreams")) .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.") .setName("DewPoint Temperature") .setDefinition("http://dbpedia.org/page/Dew_point") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -644,7 +725,7 @@ public void writeObservation_Basic_Success() throws Exception { + " \"result\": 70.40\n" + "}"; Entity entity = new ObservationBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Observations(1)") .setFeatureOfInterest(new FeatureOfInterestBuilder().setNavigationLink("Observations(1)/FeatureOfInterest").build()) .setDatastream(new DatastreamBuilder().setNavigationLink("Observations(1)/Datastream").build()) @@ -652,7 +733,31 @@ public void writeObservation_Basic_Success() throws Exception { .setResultTime(TestHelper.createTimeInstantUTC(2014, 12, 31, 19, 59, 59)) .setResult(new BigDecimal("70.40")) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); + } + + @Test + public void writeObservation_BasicWithNullResult() throws Exception { + String expResult + = "{\n" + + " \"@iot.id\": 1,\n" + + " \"@iot.selfLink\": \"http://example.org/v1.0/Observations(1)\",\n" + + " \"FeatureOfInterest@iot.navigationLink\": \"Observations(1)/FeatureOfInterest\",\n" + + " \"Datastream@iot.navigationLink\":\"Observations(1)/Datastream\",\n" + + " \"phenomenonTime\": \"2014-12-31T11:59:59.000Z\",\n" + + " \"resultTime\": \"2014-12-31T19:59:59.000Z\",\n" + + " \"result\": null\n" + + "}"; + Entity entity = new ObservationBuilder() + .setId(new IdLong(1)) + .setSelfLink("http://example.org/v1.0/Observations(1)") + .setFeatureOfInterest(new FeatureOfInterestBuilder().setNavigationLink("Observations(1)/FeatureOfInterest").build()) + .setDatastream(new DatastreamBuilder().setNavigationLink("Observations(1)/Datastream").build()) + .setPhenomenonTime(TestHelper.createTimeInstantUTC(2014, 12, 31, 11, 59, 59)) + .setResultTime(TestHelper.createTimeInstantUTC(2014, 12, 31, 19, 59, 59)) + .setResult(null) + .build(); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -668,7 +773,7 @@ public void writeObservation_WithEmptyResultTime_Success() throws Exception { + " \"result\": \"70.4\"\n" + "}"; Entity entity = new ObservationBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Observations(1)") .setFeatureOfInterest(new FeatureOfInterestBuilder().setNavigationLink("Observations(1)/FeatureOfInterest").build()) .setDatastream(new DatastreamBuilder().setNavigationLink("Observations(1)/Datastream").build()) @@ -676,7 +781,7 @@ public void writeObservation_WithEmptyResultTime_Success() throws Exception { .setResultTime(new TimeInstant(null)) .setResult("70.4") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -691,7 +796,7 @@ public void writeObservation_WithEmptyDatastream_Success() throws Exception { + " \"result\": \"70.4\"\n" + "}"; Entity entity = new ObservationBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/Observations(1)") .setFeatureOfInterest(new FeatureOfInterestBuilder().setNavigationLink("Observations(1)/FeatureOfInterest").build()) .setDatastream(new DatastreamBuilder().build()) @@ -699,7 +804,7 @@ public void writeObservation_WithEmptyDatastream_Success() throws Exception { .setResultTime(TestHelper.createTimeInstantUTC(2014, 12, 31, 19, 59, 59)) .setResult("70.4") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -803,7 +908,7 @@ public void writeObservation_DataArray() throws IOException { source.getValue().add(dav2); source.getValue().add(dav3); - assert (jsonEqual(expResult, new EntityFormatter().writeObject(source))); + assert (jsonEqual(expResult, EntityFormatter.writeObject(source))); } @Test @@ -818,14 +923,14 @@ public void writeFeatureOfInterst_Basic_Success() throws Exception { + " \"encodingType\": \"application/vnd.geo+json\"" + "}"; Entity entity = new FeatureOfInterestBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/FeaturesOfInterest(1)") - .setObservations(new EntitySetImpl(EntityType.Observation, "FeaturesOfInterest(1)/Observations")) + .setObservations(new EntitySetImpl(EntityType.OBSERVATION, "FeaturesOfInterest(1)/Observations")) .setName("This is a weather station.") .setDescription("This is a weather station.") .setEncodingType("application/vnd.geo+json") .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } @Test @@ -850,19 +955,19 @@ public void writeFeatureOfInterst_WithGeoJsonPointFeature_Success() throws Excep + " }\n" + "}"; Entity entity = new FeatureOfInterestBuilder() - .setId(new LongId(1)) + .setId(new IdLong(1)) .setSelfLink("http://example.org/v1.0/FeaturesOfInterest(1)") - .setObservations(new EntitySetImpl(EntityType.Observation, "FeaturesOfInterest(1)/Observations")) + .setObservations(new EntitySetImpl(EntityType.OBSERVATION, "FeaturesOfInterest(1)/Observations")) .setName("This is a weather station.") .setDescription("This is a weather station.") .setEncodingType("application/vnd.geo+json") .setFeature(TestHelper.getFeatureWithPoint(-114.06, 51.05)) .build(); - assert (jsonEqual(expResult, new EntityFormatter().writeEntity(entity))); + assert (jsonEqual(expResult, EntityFormatter.writeEntity(entity))); } private boolean jsonEqual(String string1, String string2) { - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + ObjectMapper mapper = EntityParser.getSimpleObjectMapper(); try { JsonNode json1 = mapper.readTree(string1); JsonNode json2 = mapper.readTree(string2); diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/settings/SettingsTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/settings/SettingsTest.java new file mode 100644 index 000000000..be33703bc --- /dev/null +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/settings/SettingsTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 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.sta.settings; + +import java.util.Properties; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @author jab + */ +public class SettingsTest { + + public SettingsTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void testSettingsBase() { + Properties properties = new Properties(); + properties.setProperty("property1", "value1"); + properties.setProperty("property2", "value2"); + properties.setProperty("prefix1.property1", "value3"); + properties.setProperty("prefix1.property3", "value4"); + properties.setProperty("prefix2.property1", "value5"); + properties.setProperty("prefix2.property4", "value6"); + Settings base = new Settings(properties); + Settings prefix1 = new Settings(base.getProperties(), "prefix1.", false); + Settings prefix2 = new Settings(base.getProperties(), "prefix2.", false); + + assertEquals("value1", base.get("property1")); + assertEquals("value2", base.get("property2")); + assertEquals("value3", prefix1.get("property1")); + assertEquals("value4", prefix1.get("property3")); + assertEquals("value5", prefix2.get("property1")); + assertEquals("value6", prefix2.get("property4")); + Assert.assertTrue(base.containsName("property1")); + Assert.assertFalse(base.containsName("property3")); + Assert.assertTrue(prefix1.containsName("property1")); + Assert.assertFalse(prefix1.containsName("property2")); + Assert.assertTrue(prefix1.containsName("property3")); + Assert.assertTrue(prefix2.containsName("property1")); + Assert.assertFalse(prefix2.containsName("property3")); + Assert.assertTrue(prefix2.containsName("property4")); + } + +} diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/TestHelper.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/util/TestHelper.java similarity index 81% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/TestHelper.java rename to FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/util/TestHelper.java index 9d1e399c1..b76f4211a 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/util/TestHelper.java +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/util/TestHelper.java @@ -38,10 +38,17 @@ */ public class TestHelper { + private TestHelper() { + // Utility class, not to be instantiated. + } + public static Polygon getPolygon(int dimensions, T... values) { - assert (values != null); - assert (dimensions == 2 || dimensions == 3); - assert (values.length % dimensions == 0); + if (dimensions < 2 || dimensions > 3) { + throw new IllegalArgumentException("getPolygon requires 'demensions' to be 2 or 3."); + } + if (values == null || values.length % dimensions != 0) { + throw new IllegalArgumentException("The number of values " + values + " does not fit the dimensions " + dimensions); + } List points = new ArrayList<>(values.length / dimensions); for (int i = 0; i < values.length; i += dimensions) { if (dimensions == 2) { @@ -54,8 +61,9 @@ public static Polygon getPolygon(int dimensions, T... values) } public static Point getPoint(T... values) { - assert (values != null); - assert (values.length == 2 || values.length == 3); + if (values == null || values.length < 2 || values.length > 3) { + throw new IllegalArgumentException("values must have a length of 2 or 3."); + } if (values.length == 2) { return new Point(values[0].doubleValue(), values[1].doubleValue()); } @@ -63,8 +71,9 @@ public static Point getPoint(T... values) { } public static LineString getLine(T[]... values) { - assert (values != null); - assert (values[0].length == 2 || values[0].length == 3); + if (values == null || values.length < 2 || values.length > 3) { + throw new IllegalArgumentException("values must have a length of 2 or 3."); + } return new LineString(Arrays.asList(values).stream().map(x -> getPoint(x).getCoordinates()).toArray(size -> new LngLatAlt[size])); } @@ -73,7 +82,10 @@ public static Feature getFeatureWithPoint(T... values) { } public static Feature getFeatureWithGeometry(GeoJsonObject geometry) { - assert (geometry != null); + if (geometry == null) { + throw new IllegalArgumentException("geometry must be non-null"); + } + Feature result = new Feature(); result.setGeometry(geometry); return result; diff --git a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelperTest.java b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelperTest.java index d6f51c676..e8450aa11 100644 --- a/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelperTest.java +++ b/FROST-Server.Core/src/test/java/de/fraunhofer/iosb/ilt/sta/util/UrlHelperTest.java @@ -18,8 +18,11 @@ package de.fraunhofer.iosb.ilt.sta.util; import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManagerString; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManagerlong; import org.junit.After; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -50,7 +53,27 @@ public void tearDown() { } private void testNextLink(String baseUrl, String expectedNextUrl) { - testNextLink(IdManager.ID_MANAGER_LONG, baseUrl, expectedNextUrl); + testNextLink(new IdManagerlong(), baseUrl, expectedNextUrl); + } + + private void testNextLink(String url) { + testNextLink(new IdManagerlong(), url); + } + + private void testNextLink(IdManager idManager, String url) { + String baseUrl; + String expectedNextUrl; + if (url.contains("?")) { + baseUrl = url + "&$top=2"; + expectedNextUrl = url + "&$skip=2&$top=2"; + } else { + baseUrl = url + "?$top=2"; + expectedNextUrl = url + "?$skip=2&$top=2"; + } + testNextLink( + idManager, + baseUrl, + expectedNextUrl); } private void testNextLink(IdManager idManager, String baseUrl, String expectedNextUrl) { @@ -64,6 +87,19 @@ private void testNextLink(IdManager idManager, String baseUrl, String expectedNe assert (next.equals(queryExpected)); } + @Test + public void testEscapeForStringConstant() { + Assert.assertEquals("abcdefg", UrlHelper.escapeForStringConstant("abcdefg")); + Assert.assertEquals("''", UrlHelper.escapeForStringConstant("'")); + Assert.assertEquals("''''", UrlHelper.escapeForStringConstant("''")); + } + + @Test + public void testUrlEncode() { + Assert.assertEquals("http%3A//example.org/Things%5Bxyz%27xyz%5D", UrlHelper.urlEncode("http://example.org/Things[xyz'xyz]", true)); + Assert.assertEquals("http%3A%2F%2Fexample.org%2FThings%5Bxyz%27xyz%5D", UrlHelper.urlEncode("http://example.org/Things[xyz'xyz]", false)); + } + @Test public void testNextLink_Top_Success() { testNextLink( @@ -73,7 +109,7 @@ public void testNextLink_Top_Success() { "/Things(5)/Datastreams?$top=2", "/Things(5)/Datastreams?$top=2&$skip=2"); testNextLink( - IdManager.ID_MANAGER_STRING, + new IdManagerString(), "/Things('a String Id')/Datastreams?$top=2", "/Things('a String Id')/Datastreams?$top=2&$skip=2"); } @@ -147,7 +183,22 @@ public void testNextLink_Success() { "/Things?" + base + "&$top=2&$skip=2"); } } + // TODO: Add all filters + @Test + public void testNextLink_Filter() { + testNextLink( + "/Things?$filter=id eq 1"); + testNextLink( + new IdManagerString(), + "/Things?$filter=id eq 'one'&$top=2"); + testNextLink( + "/Things?$filter=properties/prop1 eq 1&$top=2"); + testNextLink( + "/Things?$filter=properties/prop1&$top=2"); + testNextLink( + "/Datastreams?$filter=unitOfMeasurement/name eq 'metre'&$top=2"); + } @Test public void testgetRelativePath() { diff --git a/FROST-Server.Core/src/test/resources/logback-test.xml b/FROST-Server.Core/src/test/resources/logback-test.xml index 018ee9d29..ae86b3b26 100644 --- a/FROST-Server.Core/src/test/resources/logback-test.xml +++ b/FROST-Server.Core/src/test/resources/logback-test.xml @@ -1,17 +1,16 @@ - - - %d{HH:mm:ss.SSS} [%16thread] %-5level %30logger{30} - %msg%n - - + + + %d{HH:mm:ss.SSS} [%16thread] %-5level %30logger{30} - %msg%n + + - - - - + + + - - - + + + diff --git a/FROST-Server.HTTP.Common/nb-configuration.xml b/FROST-Server.HTTP.Common/nb-configuration.xml new file mode 100644 index 000000000..356d3f86c --- /dev/null +++ b/FROST-Server.HTTP.Common/nb-configuration.xml @@ -0,0 +1,31 @@ + + + + + + none + 4 + 4 + 4 + 80 + true + none + 4 + 4 + 8 + 80 + true + project + lgpl3 + + diff --git a/FROST-Server.HTTP.Common/pom.xml b/FROST-Server.HTTP.Common/pom.xml new file mode 100644 index 000000000..8c8d43690 --- /dev/null +++ b/FROST-Server.HTTP.Common/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + de.fraunhofer.iosb.ilt.FROST-Server + FROST-ServerParent + 1.9-SNAPSHOT + ../pom.xml + + FROST-Server.HTTP.Common + jar + + FROST-Server.HTTP.Common + HTTP bindings for the FROST-Server. + https://github.com/FraunhoferIOSB/FROST-Server + + + UTF-8 + + + + + ${project.groupId} + FROST-Server.Core + ${project.version} + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + com.google.guava + guava + ${guava.version} + + + javax + javaee-web-api + 7.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.source} + ${maven.compiler.target} + + ${endorsed.dir} + + + + + + + diff --git a/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/AbstractContextListener.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/AbstractContextListener.java new file mode 100644 index 000000000..f2492792c --- /dev/null +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/AbstractContextListener.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2016 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.http.common; + +import de.fraunhofer.iosb.ilt.sta.messagebus.MessageBusFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.settings.Settings; +import de.fraunhofer.iosb.ilt.sta.util.GitVersionInfo; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.Properties; +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jab, scf + */ +public abstract class AbstractContextListener implements ServletContextListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractContextListener.class); + public static final String TAG_CORE_SETTINGS = "CoreSettings"; + + private CoreSettings coreSettings; + + public CoreSettings getCoreSettings() { + return coreSettings; + } + + private synchronized void initCoreSettings(ServletContext context) { + if (coreSettings != null) { + return; + } + Properties properties = new Properties(); + Enumeration names = context.getInitParameterNames(); + while (names.hasMoreElements()) { + String name = names.nextElement(); + String targetName = name.replaceAll("_", "."); + properties.put(targetName, context.getInitParameter(name)); + } + + properties.setProperty(CoreSettings.TAG_TEMP_PATH, context.getAttribute(ServletContext.TEMPDIR).toString()); + coreSettings = new CoreSettings(properties); + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + GitVersionInfo.logGitInfo(); + + if (sce != null && sce.getServletContext() != null) { + LOGGER.info("Context initialised, loading settings."); + ServletContext context = sce.getServletContext(); + + initCoreSettings(context); + context.setAttribute(TAG_CORE_SETTINGS, coreSettings); + + setUpCorsFilter(context, coreSettings); + + PersistenceManagerFactory.init(coreSettings); + MessageBusFactory.init(coreSettings); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + LOGGER.info("Context destroyed, shutting down threads..."); + MessageBusFactory.getMessageBus().stop(); + try { + Thread.sleep(5000L); + } catch (InterruptedException ex) { + LOGGER.debug("Rude wakeup?", ex); + Thread.currentThread().interrupt(); + } + LOGGER.info("Context destroyed, done shutting down threads."); + } + + private void setUpCorsFilter(ServletContext servletContext, CoreSettings coreSettings) { + Settings httpSettings = coreSettings.getHttpSettings(); + boolean corsEnable = httpSettings.getBoolean(CoreSettings.TAG_CORS_ENABLE, CoreSettings.DEFAULT_CORS_ENABLE); + if (corsEnable) { + try { + String filterName = "CorsFilter"; + + FilterRegistration.Dynamic corsFilter = servletContext.addFilter(filterName, "org.apache.catalina.filters.CorsFilter"); + corsFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), true, "/*"); + + String allowedOrigins = httpSettings.get(CoreSettings.TAG_CORS_ALLOWED_ORIGINS, CoreSettings.DEFAULT_CORS_ALLOWED_ORIGINS); + corsFilter.setInitParameter("cors.allowed.origins", allowedOrigins); + + String allowedMethods = httpSettings.get(CoreSettings.TAG_CORS_ALLOWED_METHODS, CoreSettings.DEFAULT_CORS_ALLOWED_METHODS); + corsFilter.setInitParameter("cors.allowed.methods", allowedMethods); + + String exposedHeaders = httpSettings.get(CoreSettings.TAG_CORS_EXPOSED_HEADERS, CoreSettings.DEFAULT_CORS_EXPOSED_HEADERS); + corsFilter.setInitParameter("cors.exposed.headers", exposedHeaders); + + String allowedHeaders = httpSettings.get(CoreSettings.TAG_CORS_ALLOWED_HEADERS, CoreSettings.DEFAULT_CORS_ALLOWED_HEADERS); + corsFilter.setInitParameter("cors.allowed.headers", allowedHeaders); + + String supportCreds = httpSettings.get(CoreSettings.TAG_CORS_SUPPORT_CREDENTIALS, CoreSettings.DEFAULT_CORS_SUPPORT_CREDENTIALS); + corsFilter.setInitParameter("cors.support.credentials", supportCreds); + + String preflightMaxage = httpSettings.get(CoreSettings.TAG_CORS_PREFLIGHT_MAXAGE, CoreSettings.DEFAULT_CORS_PREFLIGHT_MAXAGE); + corsFilter.setInitParameter("cors.preflight.maxage", preflightMaxage); + + String requestDecorate = httpSettings.get(CoreSettings.TAG_CORS_REQUEST_DECORATE, CoreSettings.DEFAULT_CORS_REQUEST_DECORATE); + corsFilter.setInitParameter("cors.request.decorate", requestDecorate); + } catch (Exception exc) { + LOGGER.error("Failed to initialise CORS filter.", exc); + } + } + } +} diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/DatabaseStatus.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/DatabaseStatus.java similarity index 83% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/DatabaseStatus.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/DatabaseStatus.java index e6bc221fc..60eecb5e9 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/DatabaseStatus.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/DatabaseStatus.java @@ -15,13 +15,13 @@ * 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.sta; +package de.fraunhofer.iosb.ilt.frostserver.http.common; import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.util.UpgradeFailedException; import java.io.IOException; import java.io.PrintWriter; -import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -48,11 +48,9 @@ public class DatabaseStatus extends HttpServlet { * * @param request servlet request * @param response servlet response - * @throws ServletException if a servlet-specific error occurs - * @throws IOException if an I/O error occurs */ - protected void processGetRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(ContextListener.TAG_CORE_SETTINGS); + protected void processGetRequest(HttpServletRequest request, HttpServletResponse response) { + CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(AbstractContextListener.TAG_CORE_SETTINGS); PersistenceManagerFactory.init(coreSettings); response.setContentType("text/html;charset=UTF-8"); @@ -79,11 +77,13 @@ protected void processGetRequest(HttpServletRequest request, HttpServletResponse out.println("

Done. Click the button to execute the listed updates.

"); out.println(""); out.println(""); + } catch (IOException exc) { + LOGGER.error("Error writing output to client", exc); } } - protected void processPostRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(ContextListener.TAG_CORE_SETTINGS); + protected void processPostRequest(HttpServletRequest request, HttpServletResponse response) { + CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(AbstractContextListener.TAG_CORE_SETTINGS); PersistenceManagerFactory.init(coreSettings); response.setContentType("text/html;charset=UTF-8"); @@ -97,13 +97,22 @@ protected void processPostRequest(HttpServletRequest request, HttpServletRespons out.println("

Servlet DatabaseStatus at " + request.getContextPath() + "

Updating Database

"); out.println("
");
-            String log = PersistenceManagerFactory.getInstance().create().doUpgrades();
-            out.println(log);
+            processUpgrade(out);
             out.println("
"); out.println("

Done. Back...

"); out.println(""); out.println(""); + } catch (IOException exc) { + LOGGER.error("Error writing output to client", exc); + } + } + + private void processUpgrade(final PrintWriter out) throws IOException { + try { + PersistenceManagerFactory.getInstance().create().doUpgrades(out); + } catch (UpgradeFailedException ex) { + LOGGER.error("Could not initialise database.", ex); } } @@ -112,12 +121,9 @@ protected void processPostRequest(HttpServletRequest request, HttpServletRespons * * @param request servlet request * @param response servlet response - * @throws ServletException if a servlet-specific error occurs - * @throws IOException if an I/O error occurs */ @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + protected void doGet(HttpServletRequest request, HttpServletResponse response) { processGetRequest(request, response); } @@ -126,12 +132,9 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) * * @param request servlet request * @param response servlet response - * @throws ServletException if a servlet-specific error occurs - * @throws IOException if an I/O error occurs */ @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse response) { processPostRequest(request, response); } diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/Servlet_1_0.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/ServletV1P0.java similarity index 72% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/Servlet_1_0.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/ServletV1P0.java index 6bd26af8b..4ac9e9a17 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/Servlet_1_0.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/ServletV1P0.java @@ -15,23 +15,20 @@ * 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.sta; +package de.fraunhofer.iosb.ilt.frostserver.http.common; import com.google.common.base.Strings; -import de.fraunhofer.iosb.ilt.sta.multipart.BatchProcessor; -import de.fraunhofer.iosb.ilt.sta.multipart.MixedContent; +import de.fraunhofer.iosb.ilt.frostserver.http.common.multipart.BatchProcessor; +import de.fraunhofer.iosb.ilt.frostserver.http.common.multipart.MixedContent; import de.fraunhofer.iosb.ilt.sta.service.RequestType; import de.fraunhofer.iosb.ilt.sta.service.Service; import de.fraunhofer.iosb.ilt.sta.service.ServiceRequest; import de.fraunhofer.iosb.ilt.sta.service.ServiceRequestBuilder; import de.fraunhofer.iosb.ilt.sta.service.ServiceResponse; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; import java.io.BufferedReader; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URLDecoder; -import java.nio.charset.Charset; import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; @@ -55,33 +52,33 @@ } ) @MultipartConfig() -public class Servlet_1_0 extends HttpServlet { +public class ServletV1P0 extends HttpServlet { /** * The logger for this class. */ - private static final Logger LOGGER = LoggerFactory.getLogger(Servlet_1_0.class); - private static final Charset ENCODING = Charset.forName("UTF-8"); + private static final Logger LOGGER = LoggerFactory.getLogger(ServletV1P0.class); + private static final String ENCODING = "UTF-8"; - private void processGetRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + private void processGetRequest(HttpServletRequest request, HttpServletResponse response) { response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding(ENCODING); String pathInfo = request.getPathInfo(); if (Strings.isNullOrEmpty(pathInfo) || pathInfo.equals("/")) { - executeService(RequestType.GetCapabilities, request, response); + executeService(RequestType.GET_CAPABILITIES, request, response); } else { - executeService(RequestType.Read, request, response); + executeService(RequestType.READ, request, response); } } - private void processPostRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + private void processPostRequest(HttpServletRequest request, HttpServletResponse response) { String urlPath = request.getPathInfo(); if (null == urlPath) { - executeService(RequestType.Create, request, response); + executeService(RequestType.CREATE, request, response); } else { switch (urlPath) { case "/CreateObservations": - executeService(RequestType.CreateObservations, request, response); + executeService(RequestType.CREATE_OBSERVATIONS, request, response); break; case "/$batch": @@ -89,26 +86,26 @@ private void processPostRequest(HttpServletRequest request, HttpServletResponse break; default: - executeService(RequestType.Create, request, response); + executeService(RequestType.CREATE, request, response); break; } } } - private void processPatchRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - executeService(RequestType.UpdateChanges, request, response); + private void processPatchRequest(HttpServletRequest request, HttpServletResponse response) { + executeService(RequestType.UPDATE_CHANGES, request, response); } - private void processPutRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - executeService(RequestType.UpdateAll, request, response); + private void processPutRequest(HttpServletRequest request, HttpServletResponse response) { + executeService(RequestType.UPDATE_ALL, request, response); } - private void processDeleteRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - executeService(RequestType.Delete, request, response); + private void processDeleteRequest(HttpServletRequest request, HttpServletResponse response) { + executeService(RequestType.DELETE, request, response); } private void processBatchRequest(HttpServletRequest request, HttpServletResponse response) { - CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(ContextListener.TAG_CORE_SETTINGS); + CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(AbstractContextListener.TAG_CORE_SETTINGS); Service service = new Service(coreSettings); MixedContent multipartMixedData = new MixedContent(false); @@ -121,7 +118,7 @@ private void sendMixedResponse(MixedContent multipartMixedData, HttpServletRespo httpResponse.setStatus(200); multipartMixedData.getHeaders().entrySet().forEach(x -> httpResponse.setHeader(x.getKey(), x.getValue())); try { - httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.setCharacterEncoding(ENCODING); httpResponse.getWriter().write(multipartMixedData.getContent(false)); } catch (IOException ex) { LOGGER.error("Error writing HTTP result", ex); @@ -129,25 +126,36 @@ private void sendMixedResponse(MixedContent multipartMixedData, HttpServletRespo } } - private void executeService(RequestType requestType, HttpServletRequest request, HttpServletResponse response) throws MalformedURLException, IOException { + private void executeService(RequestType requestType, HttpServletRequest request, HttpServletResponse response) { try { - CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(ContextListener.TAG_CORE_SETTINGS); + CoreSettings coreSettings = (CoreSettings) request.getServletContext().getAttribute(AbstractContextListener.TAG_CORE_SETTINGS); Service service = new Service(coreSettings); sendResponse(service.execute(serviceRequestFromHttpRequest(request, requestType)), response); - } catch (Exception e) { - LOGGER.error("", e); - sendResponse(new ServiceResponse(500, e.getMessage()), response); + } catch (Exception exc) { + LOGGER.error("", exc); + sendResponse(new ServiceResponse(500, exc.getMessage()), response); } } - private ServiceRequest serviceRequestFromHttpRequest(HttpServletRequest request, RequestType requestType) throws UnsupportedEncodingException, IOException { + private ServiceRequest serviceRequestFromHttpRequest(HttpServletRequest request, RequestType requestType) throws IOException { + // request.getPathInfo() is decoded, breaking urls that contain // + // (ids that are urls) + String requestURI = request.getRequestURI(); + String contextPath = request.getContextPath(); + String servletPath = request.getServletPath(); + String fullPath = contextPath + servletPath; + String pathInfo; + if (requestURI.startsWith(fullPath)) { + pathInfo = UrlHelper.urlDecode(requestURI.substring(fullPath.length())); + } else { + pathInfo = request.getPathInfo(); + } + return new ServiceRequestBuilder() .withRequestType(requestType) - .withUrlPath(request.getPathInfo() != null - ? URLDecoder.decode(request.getPathInfo(), ENCODING.name()) - : null) + .withUrlPath(pathInfo) .withUrlQuery(request.getQueryString() != null - ? URLDecoder.decode(request.getQueryString(), ENCODING.name()) + ? UrlHelper.urlDecode(request.getQueryString()) : null) .withContent(readRequestData(request.getReader())) .build(); @@ -162,7 +170,7 @@ private void sendResponse(ServiceResponse serviceResponse, HttpServletRespons && serviceResponse.getResultFormatted() != null && !serviceResponse.getResultFormatted().isEmpty()) { httpResponse.setContentType("application/json"); - httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.setCharacterEncoding(ENCODING); httpResponse.getWriter().write(serviceResponse.getResultFormatted()); } else if (serviceResponse.getMessage() != null @@ -176,26 +184,26 @@ private void sendResponse(ServiceResponse serviceResponse, HttpServletRespons } @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + protected void doGet(HttpServletRequest request, HttpServletResponse response) { processGetRequest(request, response); } @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse response) { processPostRequest(request, response); } @Override - protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + protected void doPut(HttpServletRequest request, HttpServletResponse response) { processPutRequest(request, response); } - protected void doPatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + protected void doPatch(HttpServletRequest request, HttpServletResponse response) { processPatchRequest(request, response); } @Override - protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + protected void doDelete(HttpServletRequest request, HttpServletResponse response) { processDeleteRequest(request, response); } @@ -218,7 +226,7 @@ protected void service(HttpServletRequest request, HttpServletResponse response) super.service(request, response); } - private String readRequestData(BufferedReader reader) throws IOException { + private String readRequestData(BufferedReader reader) { return reader.lines().collect(Collectors.joining("\n")); } } diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/BatchProcessor.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/BatchProcessor.java similarity index 92% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/BatchProcessor.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/BatchProcessor.java index 4a260d363..9d84bbf11 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/BatchProcessor.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/BatchProcessor.java @@ -1,20 +1,21 @@ /* - * Copyright (C) 2016 Fraunhofer IOSB + * Copyright (C) 2016 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 General Public License as published by + * 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License + * 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.sta.multipart; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; import com.google.common.base.Strings; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; @@ -37,6 +38,10 @@ */ public class BatchProcessor { + private BatchProcessor() { + // Utility class, not to be instantiated. + } + /** * The logger for this class. */ @@ -51,7 +56,7 @@ public static HttpContent processHttpRequest(Service service, HttpContent httpRe .build(); ServiceResponse serviceResponse = service.execute(serviceRequest); - if (type == RequestType.Create) { + if (type == RequestType.CREATE) { Object createdObject = serviceResponse.getResult(); if (createdObject instanceof Entity) { Entity entity = (Entity) createdObject; diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Content.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Content.java similarity index 76% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Content.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Content.java index 9402c45c5..2badf234b 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Content.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Content.java @@ -1,4 +1,4 @@ -package de.fraunhofer.iosb.ilt.sta.multipart; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; import java.util.List; import java.util.Map; @@ -9,6 +9,9 @@ */ public interface Content { + /** + * Indicates the parse-state of the Content. + */ public enum IsFinished { /** * The Content does not expect any more lines. @@ -51,8 +54,20 @@ public enum IsFinished { */ public void stripLastNewline(); + /** + * Gives the parse-state of the Content. This indicates if more content is + * expected or not. + * + * @return the parse-state of the Content. + */ public IsFinished isFinished(); + /** + * Sets the indentation of log messages. Since Content can be nested, this + * makes debug output better readable. + * + * @param logIndent the indentation of log messages. + */ public void setLogIndent(String logIndent); /** diff --git a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/EntityChangeListener.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/ContentIdPair.java similarity index 71% rename from FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/EntityChangeListener.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/ContentIdPair.java index c1979b96e..0693add0d 100644 --- a/FROST-Server.Core/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/EntityChangeListener.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/ContentIdPair.java @@ -15,19 +15,20 @@ * 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.sta.persistence; - -import java.util.EventListener; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; /** * - * @author jab + * @author scf */ -public interface EntityChangeListener extends EventListener { +public final class ContentIdPair { - public void entityInserted(EntityChangedEvent e); + final String key; + final String value; - public void entityDeleted(EntityChangedEvent e); + public ContentIdPair(String key, String value) { + this.key = key; + this.value = value; + } - public void entityUpdated(EntityChangedEvent e); } diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Headers.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Headers.java similarity index 98% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Headers.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Headers.java index f7cfbb78d..365ef6bb1 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Headers.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Headers.java @@ -15,7 +15,7 @@ * 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.sta.multipart; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; import com.google.common.base.Strings; import java.util.HashMap; @@ -54,7 +54,7 @@ public class Headers { private static interface Validator { public boolean validate(String value); - }; + } /** * A Validator that uses regular expression matching. @@ -72,7 +72,7 @@ public boolean validate(String value) { return pattern.matcher(value).matches(); } - }; + } private static final Map validators = new HashMap<>(); diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/HttpContent.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/HttpContent.java similarity index 81% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/HttpContent.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/HttpContent.java index 06a1e4fd0..8ba175577 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/HttpContent.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/HttpContent.java @@ -1,4 +1,4 @@ -package de.fraunhofer.iosb.ilt.sta.multipart; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; import com.google.common.base.Strings; import de.fraunhofer.iosb.ilt.sta.service.RequestType; @@ -26,14 +26,20 @@ public class HttpContent implements Content { private static final String VERSION_REGEX = "/v[0-9]\\.[0-9](/|$)"; private static final Pattern VERSION_PATTERN = Pattern.compile(VERSION_REGEX); - private static enum State { + /** + * The different states the parser can have. + */ + private enum State { PREHEADERS, COMMAND, HEADERS, DATA } - private static enum Command { + /** + * The different http commands that can be embedded in a batch request. + */ + private enum Command { GET, PATCH, POST, @@ -44,6 +50,7 @@ public static Command fromString(String input) { return Command.valueOf(input.trim().toUpperCase()); } } + private String logIndent = ""; private State parseState = State.PREHEADERS; @@ -81,35 +88,15 @@ public HttpContent(boolean requireContentId) { public void parseLine(String line) { switch (parseState) { case PREHEADERS: - if (line.trim().isEmpty()) { - parseState = State.COMMAND; - if (requireContentId) { - contentId = headersOuter.get("content-id"); - if (Strings.isNullOrEmpty(contentId)) { - parseFailed = true; - errors.add("All Changeset parts must have a valid content-id header."); - } - } - } else { - Headers.addHeader(line, headersOuter, logIndent); - } + parsePreHeaderLine(line); break; case COMMAND: - if (line.trim().isEmpty()) { - LOGGER.warn("{}Found extra empty line before command.", logIndent); - } else { - parseCommand(line); - parseState = State.HEADERS; - } + parseCommandLine(line); break; case HEADERS: - if (line.trim().isEmpty()) { - parseState = State.DATA; - } else { - Headers.addHeader(line, headersInner, logIndent); - } + parseHeaderLine(line); break; case DATA: @@ -122,6 +109,38 @@ public void parseLine(String line) { } } + private void parsePreHeaderLine(String line) { + if (line.trim().isEmpty()) { + parseState = State.COMMAND; + if (requireContentId) { + contentId = headersOuter.get("content-id"); + if (Strings.isNullOrEmpty(contentId)) { + parseFailed = true; + errors.add("All Changeset parts must have a valid content-id header."); + } + } + } else { + Headers.addHeader(line, headersOuter, logIndent); + } + } + + private void parseCommandLine(String line) { + if (line.trim().isEmpty()) { + LOGGER.warn("{}Found extra empty line before command.", logIndent); + } else { + parseCommand(line); + parseState = State.HEADERS; + } + } + + private void parseHeaderLine(String line) { + if (line.trim().isEmpty()) { + parseState = State.DATA; + } else { + Headers.addHeader(line, headersInner, logIndent); + } + } + @Override public boolean isParseFailed() { return parseFailed; @@ -161,7 +180,7 @@ private void parseCommand(String line) { } } else { if (fullUrl.contains("$")) { - LOGGER.debug("{}Url with no version, but possible replace pattern.", logIndent, fullUrl); + LOGGER.debug("{}Url with no version, but possible replace pattern: {}", logIndent, fullUrl); path = fullUrl; } else { LOGGER.error("{}Url contains no version number: {}", logIndent, fullUrl); @@ -176,26 +195,26 @@ public RequestType getRequestType() { case GET: if (path.length() <= 6) { // Only the version number in the path (/v1.0) - return RequestType.GetCapabilities; + return RequestType.GET_CAPABILITIES; } - return RequestType.Read; + return RequestType.READ; case PATCH: - return RequestType.UpdateChanges; + return RequestType.UPDATE_CHANGES; case POST: if (path.length() < 25 && path.endsWith("/CreateObservations")) { - return RequestType.CreateObservations; + return RequestType.CREATE_OBSERVATIONS; } else if (path.length() < 25 && path.endsWith("/$batch")) { throw new IllegalArgumentException("Nested batch request not allowed."); } - return RequestType.Create; + return RequestType.CREATE; case PUT: - return RequestType.UpdateAll; + return RequestType.UPDATE_ALL; case DELETE: - return RequestType.Delete; + return RequestType.DELETE; default: LOGGER.error("Unhandled command type: {}", command); diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/MixedContent.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/MixedContent.java similarity index 73% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/MixedContent.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/MixedContent.java index 1e88ee86e..3a045c0cd 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/MixedContent.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/MixedContent.java @@ -15,7 +15,7 @@ * 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.sta.multipart; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; import com.google.common.base.Strings; import java.io.BufferedReader; @@ -45,9 +45,12 @@ public class MixedContent implements Content { public static final Pattern HEADER_PATTERN = Pattern.compile(HEADER_REGEX); public static final String SUB_HEADER_REGEX = "([-A-Za-z]+)=([^;]+)(;[ ]*)?"; public static final Pattern SUB_HEADER_PATTERN = Pattern.compile(SUB_HEADER_REGEX); - private final static char[] BOUNDARY_CHARS = "-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); + private static final char[] BOUNDARY_CHARS = "-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); - private static enum State { + /** + * The different states the parser can have. + */ + private enum State { PREAMBLE, PARTCONTENT, PARTDONE, @@ -58,6 +61,7 @@ private static enum State { * The logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(MixedContent.class); + private static final Random RAND = new Random(); private String boundary; private String boundaryPart; @@ -98,6 +102,7 @@ public boolean parse(HttpServletRequest request) { while (finished != IsFinished.FINISHED && (line = reader.readLine()) != null) { parseLine(line); } + return true; } catch (IOException exc) { LOGGER.error("Failed to read data.", exc); return false; @@ -108,10 +113,8 @@ public boolean parse(HttpServletRequest request) { } } catch (IOException exc) { LOGGER.error("Failed to close reader.", exc); - return false; } } - return true; } public MixedContent setBoundaryHeader(String boundaryHeader) { @@ -122,7 +125,7 @@ public MixedContent setBoundaryHeader(String boundaryHeader) { } @Override - public void parseLine(String line) throws IllegalStateException { + public void parseLine(String line) { try { parseLineInternal(line); } catch (IllegalArgumentException exc) { @@ -132,60 +135,19 @@ public void parseLine(String line) throws IllegalStateException { } } - public void parseLineInternal(String line) throws IllegalStateException { + public void parseLineInternal(String line) { LOGGER.trace("{}Read line: {}", logIndent, line); switch (state) { case PREAMBLE: - if (boundaryPart.equals(line.trim())) { - setState(State.PARTCONTENT); - currentPart = new Part(isChangeSet).setLogIndent(logIndent + " "); - } + parsePreamble(line); break; case PARTCONTENT: - if (currentPart == null) { - LOGGER.error("{}Content without part: {}", logIndent, line); - } - - boolean checkBoundary = currentPart.isFinished() != IsFinished.UNFINISHED; - if (checkBoundary && boundaryPart.equals(line.trim())) { - LOGGER.debug("{}Found new part", logIndent); - if (currentPart != null) { - currentPart.stripLastNewline(); - parts.add(currentPart); - } - currentPart = new Part(isChangeSet).setLogIndent(logIndent + " "); - setState(State.PARTCONTENT); - - } else if (checkBoundary && boundaryEnd.equals(line.trim())) { - LOGGER.debug("{}Found end of multipart content", logIndent); - currentPart.stripLastNewline(); - parts.add(currentPart); - currentPart = null; - finishParsing(); - - } else { - currentPart.appendLine(line); - if (currentPart.isFinished() == IsFinished.FINISHED) { - LOGGER.debug("{}Part declared done", logIndent); - parts.add(currentPart); - currentPart = null; - setState(State.PARTDONE); - } - } + parsePartContent(line); break; case PARTDONE: - if (boundaryPart.equals(line.trim())) { - LOGGER.debug("{}Found new part", logIndent); - currentPart = new Part(isChangeSet).setLogIndent(logIndent + " "); - setState(State.PARTCONTENT); - } else if (boundaryEnd.equals(line.trim())) { - LOGGER.debug("{}Found end of multipart content", logIndent); - finishParsing(); - } else if (!Strings.isNullOrEmpty(line)) { - LOGGER.warn("{}Ignoring line: {}", logIndent, line); - } + parsePartDone(line); break; case EPILOGUE: @@ -198,6 +160,57 @@ public void parseLineInternal(String line) throws IllegalStateException { } } + private void parsePreamble(String line) { + if (boundaryPart.equals(line.trim())) { + setState(State.PARTCONTENT); + currentPart = new Part(isChangeSet).setLogIndent(logIndent + " "); + } + } + + private void parsePartContent(String line) { + if (currentPart == null) { + LOGGER.error("{}Content without part: {}", logIndent, line); + return; + } + boolean checkBoundary = currentPart.isFinished() != IsFinished.UNFINISHED; + if (checkBoundary && boundaryPart.equals(line.trim())) { + LOGGER.debug("{}Found new part", logIndent); + currentPart.stripLastNewline(); + parts.add(currentPart); + currentPart = new Part(isChangeSet).setLogIndent(logIndent + " "); + setState(State.PARTCONTENT); + + } else if (checkBoundary && boundaryEnd.equals(line.trim())) { + LOGGER.debug("{}Found end of multipart content", logIndent); + currentPart.stripLastNewline(); + parts.add(currentPart); + currentPart = null; + finishParsing(); + + } else { + currentPart.appendLine(line); + if (currentPart.isFinished() == IsFinished.FINISHED) { + LOGGER.debug("{}Part declared done", logIndent); + parts.add(currentPart); + currentPart = null; + setState(State.PARTDONE); + } + } + } + + private void parsePartDone(String line) { + if (boundaryPart.equals(line.trim())) { + LOGGER.debug("{}Found new part", logIndent); + currentPart = new Part(isChangeSet).setLogIndent(logIndent + " "); + setState(State.PARTCONTENT); + } else if (boundaryEnd.equals(line.trim())) { + LOGGER.debug("{}Found end of multipart content", logIndent); + finishParsing(); + } else if (!Strings.isNullOrEmpty(line)) { + LOGGER.warn("{}Ignoring line: {}", logIndent, line); + } + } + private void finishParsing() { setState(State.EPILOGUE); finished = IsFinished.FINISHED; @@ -282,9 +295,8 @@ public Map getHeaders() { private void generateBoundary() { StringBuilder retval = new StringBuilder(); - Random rand = new Random(); for (int i = 0; i < 40; i++) { - retval.append(BOUNDARY_CHARS[rand.nextInt(BOUNDARY_CHARS.length)]); + retval.append(BOUNDARY_CHARS[RAND.nextInt(BOUNDARY_CHARS.length)]); } boundary = retval.toString(); boundaryPart = "--" + boundary; diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Part.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Part.java similarity index 77% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Part.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Part.java index 7370dd35e..4c8e63e1f 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/Part.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/Part.java @@ -15,10 +15,10 @@ * 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.sta.multipart; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; import com.google.common.base.Strings; -import de.fraunhofer.iosb.ilt.sta.multipart.Content.IsFinished; +import de.fraunhofer.iosb.ilt.frostserver.http.common.multipart.Content.IsFinished; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -31,7 +31,10 @@ */ public class Part { - private static enum State { + /** + * The different states the parser can have. + */ + private enum State { INITIAL, DATA, DONE @@ -50,10 +53,22 @@ private static enum State { private final boolean inChangeSet; + /** + * Creates a new Part. + * + * @param inChangeSet flag indicating the Part is part of a ChangeSet, and + * thus if the part itself can be a ChangeSet. + */ public Part(boolean inChangeSet) { this.inChangeSet = inChangeSet; } + /** + * Gives the parse-state of the Content in this Part. This indicates if more + * content is expected or not. + * + * @return the parse-state of the Content. + */ public IsFinished isFinished() { if (content == null) { return Content.IsFinished.UNKNOWN; @@ -61,11 +76,17 @@ public IsFinished isFinished() { return content.isFinished(); } + /** + * Get the value of the header with the given name. + * + * @param name The name of the header to get. + * @return The value of the header with the given name. + */ public String getHeader(String name) { return headers.get(name); } - public void addHeader(String line) { + private void addHeader(String line) { Matcher matcher = MixedContent.HEADER_PATTERN.matcher(line); if (matcher.find()) { String name = matcher.group(1).trim().toLowerCase(); @@ -92,9 +113,9 @@ public void addHeader(String line) { } /** - * Add the line to the Part. + * Parse the given line and add it to the Part. * - * @param line + * @param line the line to parse. */ public void appendLine(String line) { switch (parseState) { @@ -153,6 +174,10 @@ private void setParseState(State parseState) { LOGGER.debug("{}Now in state: {}", logIndent, parseState); } + /** + * Informs the Content that the last newline should be removed again. The + * newline before a boundary is part of the boundary, not of the content. + */ public void stripLastNewline() { if (content == null) { return; @@ -160,15 +185,33 @@ public void stripLastNewline() { content.stripLastNewline(); } + /** + * Get the Content of this Part. + * + * @return the Content of this Part. + */ public Content getContent() { return content; } + /** + * Set the Content of this Part. + * + * @param content the Content of this Part. + * @return this. + */ public Part setContent(Content content) { this.content = content; return this; } + /** + * Sets the indentation of log messages. Since Content can be nested, this + * makes debug output better readable. + * + * @param logIndent the indentation of log messages. + * @return this. + */ public Part setLogIndent(String logIndent) { this.logIndent = logIndent; return this; diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/StringContent.java b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/StringContent.java similarity index 96% rename from FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/StringContent.java rename to FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/StringContent.java index 707a7438b..e1ffc7446 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/StringContent.java +++ b/FROST-Server.HTTP.Common/src/main/java/de/fraunhofer/iosb/ilt/frostserver/http/common/multipart/StringContent.java @@ -1,4 +1,4 @@ -package de.fraunhofer.iosb.ilt.sta.multipart; +package de.fraunhofer.iosb.ilt.frostserver.http.common.multipart; import java.util.Collections; import java.util.List; diff --git a/FROST-Server.HTTP/nb-configuration.xml b/FROST-Server.HTTP/nb-configuration.xml index b73799ab0..b7722cb1c 100644 --- a/FROST-Server.HTTP/nb-configuration.xml +++ b/FROST-Server.HTTP/nb-configuration.xml @@ -29,5 +29,6 @@ 80 true project + false diff --git a/FROST-Server.HTTP/pom.xml b/FROST-Server.HTTP/pom.xml index 3a21b2bf4..8ae5c2fe2 100644 --- a/FROST-Server.HTTP/pom.xml +++ b/FROST-Server.HTTP/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT ../pom.xml FROST-Server.HTTP @@ -16,7 +16,7 @@ UTF-8 - fraunhoferiosb/frost-server + fraunhoferiosb/frost-server-http @@ -27,27 +27,27 @@ ${project.groupId} - FROST-Server.SQL + FROST-Server.HTTP.Common ${project.version} ${project.groupId} - FROST-Server.SQL.PGLong + FROST-Server.SQL ${project.version} ${project.groupId} - FROST-Server.SQL.PGString + FROST-Server.SQL.PGLong ${project.version} ${project.groupId} - FROST-Server.SQL.PGUuid + FROST-Server.SQL.PGString ${project.version} ${project.groupId} - FROST-Server.MQTT.Moquette + FROST-Server.SQL.PGUuid ${project.version} @@ -73,7 +73,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} @@ -84,7 +84,7 @@ maven-war-plugin - 3.2.0 + ${maven-war-plugin.version} true true @@ -94,7 +94,7 @@ org.apache.maven.plugins maven-dependency-plugin - 2.6 + ${maven-dependency-plugin.version} validate @@ -135,7 +135,7 @@ tag - ${project.version} + ${env.TAG} @@ -155,7 +155,7 @@ push - ${project.version} + ${env.TAG} diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/ContextListener.java b/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/ContextListener.java index ef2b67b1d..46daf45b0 100644 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/ContextListener.java +++ b/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/ContextListener.java @@ -1,76 +1,29 @@ -/* - * Copyright (C) 2016 Fraunhofer IOSB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.fraunhofer.iosb.ilt.sta; - -import de.fraunhofer.iosb.ilt.sta.mqtt.MqttManager; -import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; -import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; -import java.net.URI; -import java.util.Enumeration; -import java.util.Properties; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.servlet.annotation.WebListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author jab - */ -@WebListener -public class ContextListener implements ServletContextListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(ContextListener.class); - public static final String TAG_CORE_SETTINGS = "CoreSettings"; - - @Override - public void contextInitialized(ServletContextEvent sce) { - if (sce != null && sce.getServletContext() != null) { - LOGGER.info("Context initialised, loading settings."); - ServletContext context = sce.getServletContext(); - Properties properties = new Properties(); - Enumeration names = context.getInitParameterNames(); - while (names.hasMoreElements()) { - String name = names.nextElement(); - properties.put(name, context.getInitParameter(name)); - } - CoreSettings coreSettings = new CoreSettings( - properties, - URI.create(properties.getProperty(CoreSettings.TAG_SERVICE_ROOT_URL) + "/" + properties.getProperty(CoreSettings.TAG_API_VERSION)).normalize().toString(), - context.getAttribute(ServletContext.TEMPDIR).toString()); - context.setAttribute(TAG_CORE_SETTINGS, coreSettings); - PersistenceManagerFactory.init(coreSettings); - MqttManager.init(coreSettings); - PersistenceManagerFactory.addEntityChangeListener(MqttManager.getInstance()); - } - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - LOGGER.info("Context destroyed, shutting down threads..."); - MqttManager.shutdown(); - try { - Thread.sleep(5000L); - } catch (InterruptedException ex) { - LOGGER.debug("Rude wakeup?", ex); - } - LOGGER.info("Context destroyed, done shutting down threads."); - } - -} +/* + * Copyright (C) 2016 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.sta; + +import de.fraunhofer.iosb.ilt.frostserver.http.common.AbstractContextListener; +import javax.servlet.annotation.WebListener; + +/** + * @author jab, scf + */ +@WebListener +public class ContextListener extends AbstractContextListener { + // Only adds the annotation. +} diff --git a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/ContentIdPair.java b/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/ContentIdPair.java deleted file mode 100644 index a4a317854..000000000 --- a/FROST-Server.HTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/multipart/ContentIdPair.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2016 Fraunhofer IOSB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.fraunhofer.iosb.ilt.sta.multipart; - -/** - * - * @author scf - */ -public final class ContentIdPair { - - final String key; - final String value; - - public ContentIdPair(String key, String value) { - this.key = key; - this.value = value; - } - -} diff --git a/FROST-Server.HTTP/src/main/resources/logback.xml b/FROST-Server.HTTP/src/main/resources/logback.xml index 20c34ac2c..d4807b58d 100644 --- a/FROST-Server.HTTP/src/main/resources/logback.xml +++ b/FROST-Server.HTTP/src/main/resources/logback.xml @@ -28,11 +28,10 @@ - + - - + diff --git a/FROST-Server.HTTP/src/main/webapp/META-INF/context.xml b/FROST-Server.HTTP/src/main/webapp/META-INF/context.xml index 0d19e6028..e5cb4de37 100644 --- a/FROST-Server.HTTP/src/main/webapp/META-INF/context.xml +++ b/FROST-Server.HTTP/src/main/webapp/META-INF/context.xml @@ -1,5 +1,5 @@ - + - + - - - - - - - - - - - - + + + + + + diff --git a/FROST-Server.HTTP/src/main/webapp/WEB-INF/web.xml b/FROST-Server.HTTP/src/main/webapp/WEB-INF/web.xml index 362eecffc..fcdfe1a2f 100644 --- a/FROST-Server.HTTP/src/main/webapp/WEB-INF/web.xml +++ b/FROST-Server.HTTP/src/main/webapp/WEB-INF/web.xml @@ -2,6 +2,7 @@ + + The version tag of the API used in the URL. @@ -43,7 +45,7 @@ The base URL of the SensorThings Server without version. serviceRootUrl - http://localhost:8080/FROST + http://localhost:8080/FROST-Server The default value for the $count query option. @@ -75,57 +77,6 @@ mqtt.mqttServerImplementationClass de.fraunhofer.iosb.ilt.sensorthingsserver.mqtt.moquette.MoquetteMqttServer - - Specifies wether MQTT support will be enabled or not. - mqtt.Enabled - true - - - The port the MQTT server runs on. - mqtt.Port - 1883 - - - Quality of Service Level for MQTT messages. - mqtt.QoS - 0 - - - Queue size for messages to be pubslihed via MQTT. - mqtt.SubscribeMessageQueueSize - 100 - - - Number of threads use to dispatch MQTT notifications. - mqtt.SubscribeThreadPoolSize - 20 - - - Queue size for create observation requests via MQTT. - mqtt.CreateMessageQueueSize - 100 - - - Number of threads use to dispatch observation creation requests. - mqtt.CreateThreadPoolSize - 10 - - - The external IP address or host name the MQTT server should listen on. Set to 0.0.0.0 to listen on all interfaces. - mqtt.Host - 0.0.0.0 - - - The internal host name of the MQTT server. - mqtt.internalHost - localhost - - - - The port the MQTT server is reachable via WebSocket. - mqtt.WebsocketPort - 9876 - The java class used for persistence (must implement PersistenceManaher interface) @@ -137,6 +88,11 @@ de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.PostgresPersistenceManagerUuid --> + + Automatically apply database updates. + persistence.autoUpdateDatabase + false + Always add an 'orderby=id asc' to queries to ensure consistent paging. persistence.alwaysOrderbyId diff --git a/FROST-Server.HTTP/src/main/webapp/index.html b/FROST-Server.HTTP/src/main/webapp/index.html index d7b4b5e1c..d229db5c5 100644 --- a/FROST-Server.HTTP/src/main/webapp/index.html +++ b/FROST-Server.HTTP/src/main/webapp/index.html @@ -32,7 +32,7 @@

FROST-Server

Some Links

Database Status and Update
SensorThingsApi v1.0
- FROST-Server on GitHub + FROST-Server on GitHub diff --git a/FROST-Server.MQTT.Moquette/nb-configuration.xml b/FROST-Server.MQTT.Moquette/nb-configuration.xml new file mode 100644 index 000000000..5307a0247 --- /dev/null +++ b/FROST-Server.MQTT.Moquette/nb-configuration.xml @@ -0,0 +1,30 @@ + + + + + + none + 4 + 4 + 4 + 80 + true + none + 4 + 4 + 8 + 80 + true + project + + diff --git a/FROST-Server.MQTT.Moquette/pom.xml b/FROST-Server.MQTT.Moquette/pom.xml index a99040199..05bc6aee0 100644 --- a/FROST-Server.MQTT.Moquette/pom.xml +++ b/FROST-Server.MQTT.Moquette/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT ../pom.xml FROST-Server.MQTT.Moquette @@ -16,8 +16,20 @@ UTF-8 + 0.11 + + + + false + + bintray-andsel-maven + bintray + https://dl.bintray.com/andsel/maven + + + ${project.groupId} @@ -27,12 +39,23 @@ io.moquette moquette-broker - 0.9 + ${moquette.version} + + + org.slf4j + slf4j-log4j12 + + + + + io.moquette + moquette-mapdb-storage + ${moquette.version} org.eclipse.paho org.eclipse.paho.client.mqttv3 - 1.0.2 + ${paho.version} diff --git a/FROST-Server.MQTT.Moquette/src/main/java/de/fraunhofer/iosb/ilt/sensorthingsserver/mqtt/moquette/MoquetteMqttServer.java b/FROST-Server.MQTT.Moquette/src/main/java/de/fraunhofer/iosb/ilt/sensorthingsserver/mqtt/moquette/MoquetteMqttServer.java index c9fd5a6a5..cff9ea9a8 100644 --- a/FROST-Server.MQTT.Moquette/src/main/java/de/fraunhofer/iosb/ilt/sensorthingsserver/mqtt/moquette/MoquetteMqttServer.java +++ b/FROST-Server.MQTT.Moquette/src/main/java/de/fraunhofer/iosb/ilt/sensorthingsserver/mqtt/moquette/MoquetteMqttServer.java @@ -24,6 +24,8 @@ import de.fraunhofer.iosb.ilt.sta.mqtt.subscription.SubscriptionListener; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; import de.fraunhofer.iosb.ilt.sta.settings.MqttSettings; +import de.fraunhofer.iosb.ilt.sta.settings.Settings; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; import io.moquette.BrokerConstants; import io.moquette.interception.AbstractInterceptHandler; import io.moquette.interception.InterceptHandler; @@ -35,6 +37,7 @@ import io.moquette.server.Server; import io.moquette.server.config.IConfig; import io.moquette.server.config.MemoryConfig; +import io.moquette.spi.impl.subscriptions.Subscription; import java.io.IOException; import java.nio.file.Paths; import java.util.ArrayList; @@ -49,6 +52,7 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** @@ -60,17 +64,24 @@ public class MoquetteMqttServer implements MqttServer { /** * Custom Settings | Tags */ - private static final String TAG_WEBSOCKET_PORT = "WebsocketPort"; + public static final String TAG_WEBSOCKET_PORT = "WebsocketPort"; + public static final String TAG_MAX_IN_FLIGHT = "maxInFlight"; /** * Custom Settings | Default values */ - private static final int DEFAULT_WEBSOCKET_PORT = 9876; + public static final int DEFAULT_WEBSOCKET_PORT = 9876; + public static final int DEFAULT_MAX_IN_FLIGHT = 50; + public static final String DEFAULT_STORAGE_CLASS = "io.moquette.persistence.mapdb.MapDBPersistentStore"; + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(MoquetteMqttServer.class); private Server mqttBroker; private MqttClient client; protected EventListenerList subscriptionListeners = new EventListenerList(); protected EventListenerList entityCreateListeners = new EventListenerList(); - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(MoquetteMqttServer.class); private CoreSettings settings; private final Map> clientSubscriptions = new HashMap<>(); private final String clientId; @@ -151,7 +162,8 @@ public void onPublish(InterceptPublishMessage msg) { if (msg.getClientID().equalsIgnoreCase(clientId)) { return; } - fireObservationCreate(new ObservationCreateEvent(this, msg.getTopicName(), new String(msg.getPayload().array()))); + String payload = msg.getPayload().toString(StringHelper.ENCODING); + fireObservationCreate(new ObservationCreateEvent(this, msg.getTopicName(), payload)); } @Override @@ -167,9 +179,9 @@ public void onDisconnect(InterceptDisconnectMessage msg) { if (msg.getClientID().equalsIgnoreCase(clientId)) { return; } - clientSubscriptions.get(msg.getClientID()).stream().forEach((subscribedTopic) -> { - fireUnsubscribe(new SubscriptionEvent(subscribedTopic)); - }); + clientSubscriptions.get(msg.getClientID()).stream().forEach( + subscribedTopic -> fireUnsubscribe(new SubscriptionEvent(subscribedTopic)) + ); clientSubscriptions.remove(msg.getClientID()); } @@ -190,39 +202,81 @@ public void onUnsubscribe(InterceptUnsubscribeMessage msg) { clientSubscriptions.get(msg.getClientID()).remove(msg.getTopicFilter()); fireUnsubscribe(new SubscriptionEvent(msg.getTopicFilter())); } + + @Override + public String getID() { + return clientId; + } }); IConfig config = new MemoryConfig(new Properties()); MqttSettings mqttSettings = settings.getMqttSettings(); + Settings customSettings = mqttSettings.getCustomSettings(); + config.setProperty(BrokerConstants.PORT_PROPERTY_NAME, Integer.toString(mqttSettings.getPort())); config.setProperty(BrokerConstants.HOST_PROPERTY_NAME, mqttSettings.getHost()); config.setProperty(BrokerConstants.ALLOW_ANONYMOUS_PROPERTY_NAME, Boolean.TRUE.toString()); - config.setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, - Paths.get(settings.getTempPath(), - BrokerConstants.DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME).toString()); + + String defaultPersistentStore = Paths.get(settings.getTempPath(), BrokerConstants.DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME).toString(); + String persistentStore = mqttSettings.getCustomSettings().get( + BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, + defaultPersistentStore); + config.setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, persistentStore); + + String storageClass = mqttSettings.getCustomSettings().get( + BrokerConstants.STORAGE_CLASS_NAME, + DEFAULT_STORAGE_CLASS); + config.setProperty(BrokerConstants.STORAGE_CLASS_NAME, storageClass); + config.setProperty(BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, mqttSettings.getCustomSettings().getWithDefault(TAG_WEBSOCKET_PORT, DEFAULT_WEBSOCKET_PORT, Integer.class).toString()); + + int maxInFlight = customSettings.getInt(TAG_MAX_IN_FLIGHT, DEFAULT_MAX_IN_FLIGHT); try { mqttBroker.startServer(config, userHandlers); String broker = "tcp://" + mqttSettings.getInternalHost() + ":" + mqttSettings.getPort(); - try { - client = new MqttClient(broker, clientId, new MemoryPersistence()); - MqttConnectOptions connOpts = new MqttConnectOptions(); - connOpts.setCleanSession(true); - LOGGER.info("paho-client connecting to broker: " + broker); - try { - client.connect(connOpts); - client.subscribe("#"); - LOGGER.info("paho-client connected to broker"); - } catch (MqttException ex) { - LOGGER.error("Could not connect to MQTT server.", ex); - } - } catch (MqttException ex) { - LOGGER.error("Could not create MQTT Client.", ex); - } + + client = new MqttClient(broker, clientId, new MemoryPersistence()); + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setCleanSession(true); + connOpts.setKeepAliveInterval(30); + connOpts.setConnectionTimeout(30); + connOpts.setMaxInflight(maxInFlight); + LOGGER.info("paho-client connecting to broker: {}", broker); + + client.connect(connOpts); + LOGGER.info("paho-client connected to broker"); + } catch (MqttException ex) { + LOGGER.error("Could not create MQTT Client.", ex); } catch (IOException ex) { LOGGER.error("Could not start MQTT server.", ex); } + fetchOldSubscriptions(); + } + + private void fetchOldSubscriptions() { + LOGGER.info("Checking for pre-existing subscriptions."); + int count = 0; + for (Subscription sub : mqttBroker.getSubscriptions()) { + String subClientId = sub.getClientId(); + if (subClientId.equalsIgnoreCase(clientId)) { + continue; + } + String topic = sub.getTopicFilter().toString(); + LOGGER.debug("Re-subscribing existing subscription for {} on {}.", subClientId, topic); + List clientSubList = clientSubscriptions.computeIfAbsent( + subClientId, + k -> new ArrayList<>() + ); + try { + fireSubscribe(new SubscriptionEvent(topic)); + clientSubList.add(topic); + } catch (IllegalArgumentException e) { + LOGGER.warn("Exception initialising old subscription for client " + subClientId + " to topic " + topic, e); + } + count++; + } + LOGGER.info("Found {} pre-existing subscriptions.", count); } @Override diff --git a/FROST-Server.MQTT/Dockerfile b/FROST-Server.MQTT/Dockerfile new file mode 100644 index 000000000..4227330c7 --- /dev/null +++ b/FROST-Server.MQTT/Dockerfile @@ -0,0 +1,10 @@ +FROM openjdk:8 + +EXPOSE 1883 +EXPOSE 9876 + +# Copy to images tomcat path +ARG JAR_FILE +ADD target/${JAR_FILE} /usr/local/FROST/FROST-Mqtt.jar +WORKDIR /usr/local/FROST +CMD ["java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-jar", "FROST-Mqtt.jar"] diff --git a/FROST-Server.MQTT/nb-configuration.xml b/FROST-Server.MQTT/nb-configuration.xml new file mode 100644 index 000000000..356d3f86c --- /dev/null +++ b/FROST-Server.MQTT/nb-configuration.xml @@ -0,0 +1,31 @@ + + + + + + none + 4 + 4 + 4 + 80 + true + none + 4 + 4 + 8 + 80 + true + project + lgpl3 + + diff --git a/FROST-Server.MQTT/nbactions.xml b/FROST-Server.MQTT/nbactions.xml new file mode 100644 index 000000000..cc47d833e --- /dev/null +++ b/FROST-Server.MQTT/nbactions.xml @@ -0,0 +1,32 @@ + + + + run + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.2.1:exec + + + -classpath %classpath de.fraunhofer.iosb.ilt.sta.FrostMqttServer + java + + + + debug + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:1.2.1:exec + + + -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath de.fraunhofer.iosb.ilt.sta.FrostMqttServer + java + true + + + diff --git a/FROST-Server.MQTT/pom.xml b/FROST-Server.MQTT/pom.xml new file mode 100644 index 000000000..eb00d4d52 --- /dev/null +++ b/FROST-Server.MQTT/pom.xml @@ -0,0 +1,179 @@ + + + 4.0.0 + + de.fraunhofer.iosb.ilt.FROST-Server + FROST-ServerParent + 1.9-SNAPSHOT + ../pom.xml + + FROST-Server.MQTT + jar + + FROST-Server.MQTT + MQTT server part of the FROST-Server. + https://github.com/FraunhoferIOSB/FROST-Server + + + UTF-8 + de.fraunhofer.iosb.ilt.sta.FrostMqttServer + fraunhoferiosb/frost-server-mqtt + + + + + ${project.groupId} + FROST-Server.Core + ${project.version} + + + ${project.groupId} + FROST-Server.SQL + ${project.version} + + + ${project.groupId} + FROST-Server.SQL.PGLong + ${project.version} + + + ${project.groupId} + FROST-Server.SQL.PGString + ${project.version} + + + ${project.groupId} + FROST-Server.SQL.PGUuid + ${project.version} + + + ${project.groupId} + FROST-Server.MQTT.Moquette + ${project.version} + + + junit + junit + ${junit.version} + test + + + org.postgresql + postgresql + ${postgres.version} + + + net.postgis + postgis-jdbc + ${postgis.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.source} + ${maven.compiler.target} + + ${endorsed.dir} + + + + + maven-assembly-plugin + + + + ${mainClass} + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + com.spotify + dockerfile-maven-plugin + ${dockerfile-maven-plugin.version} + + + build-and-tag-latest + none + + build + + + + tag-version + none + + tag + + + ${env.TAG} + + + + push-latest + none + + push + + + latest + + + + push-version + none + + push + + + ${env.TAG} + + + + + ${docker-image-name} + + ${project.build.finalName}-jar-with-dependencies.jar + + + + + + javax.activation + activation + ${javax-activation.version} + + + + + + + diff --git a/FROST-Server.MQTT/run.sh b/FROST-Server.MQTT/run.sh new file mode 100755 index 000000000..33ddc8208 --- /dev/null +++ b/FROST-Server.MQTT/run.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# + +export serviceRootUrl=http://localhost:8080/FROST-Server +export bus_busImplementationClass=de.fraunhofer.iosb.ilt.sta.messagebus.MqttMessageBus +export bus_mqttBroker=tcp://mosquitto:1883 +export persistence_persistenceManagerImplementationClass=de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong +export persistence_alwaysOrderbyId=false +export persistence_db_driver=org.postgresql.Driver +export persistence_db_url=jdbc:postgresql://database:5432/sensorthings +export persistence_db_username=sensorthings +export persistence_db_password=ChangeMe + +java -jar target/FROST-Server.MQTT-*-SNAPSHOT-jar-with-dependencies.jar diff --git a/FROST-Server.MQTT/src/main/java/de/fraunhofer/iosb/ilt/sta/FrostMqttServer.java b/FROST-Server.MQTT/src/main/java/de/fraunhofer/iosb/ilt/sta/FrostMqttServer.java new file mode 100644 index 000000000..d9bb82838 --- /dev/null +++ b/FROST-Server.MQTT/src/main/java/de/fraunhofer/iosb/ilt/sta/FrostMqttServer.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 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.sta; + +import de.fraunhofer.iosb.ilt.sta.messagebus.MessageBusFactory; +import de.fraunhofer.iosb.ilt.sta.mqtt.MqttManager; +import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManagerFactory; +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.util.GitVersionInfo; +import de.fraunhofer.iosb.ilt.sta.util.StringHelper; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author scf + */ +public class FrostMqttServer { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(FrostMqttServer.class); + private static final String KEY_TEMP_PATH = "tempPath"; + private static final String KEY_WAIT_FOR_ENTER = "WaitForEnter"; + private static final String CONFIG_FILE_NAME = "FrostMqtt.properties"; + private final CoreSettings coreSettings; + private Thread shutdownHook; + + public FrostMqttServer(CoreSettings coreSettings) { + this.coreSettings = coreSettings; + } + + private synchronized void addShutdownHook() { + if (this.shutdownHook == null) { + this.shutdownHook = new Thread(() -> { + LOGGER.info("Shutting down..."); + try { + stop(); + } catch (Exception ex) { + LOGGER.warn("Exception stopping listeners.", ex); + } + }); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + } + + public void start() { + addShutdownHook(); + PersistenceManagerFactory.init(coreSettings); + MessageBusFactory.init(coreSettings); + MqttManager.init(coreSettings); + MessageBusFactory.getMessageBus().addMessageListener(MqttManager.getInstance()); + } + + public void stop() { + LOGGER.info("Shutting down threads..."); + MqttManager.shutdown(); + MessageBusFactory.getMessageBus().stop(); + try { + Thread.sleep(3000L); + } catch (InterruptedException ex) { + LOGGER.debug("Rude wakeup?", ex); + Thread.currentThread().interrupt(); + } + LOGGER.info("Done shutting down threads."); + } + + private static CoreSettings loadCoreSettings(String configFileName) throws IOException { + Properties defaults = new Properties(); + defaults.setProperty(KEY_TEMP_PATH, System.getProperty("java.io.tmpdir")); + Properties properties = new Properties(defaults); + try { + FileInputStream input = new FileInputStream(configFileName); + properties.load(input); + LOGGER.info("Read {} properties from {}.", properties.size(), configFileName); + } catch (IOException exc) { + LOGGER.info("Could not read properties from file: {}.", exc.getMessage()); + } + return new CoreSettings(properties); + } + + /** + * @param args the command line arguments + * @throws java.io.FileNotFoundException if the config file is not found. + */ + public static void main(String[] args) throws IOException { + GitVersionInfo.logGitInfo(); + + String configFileName = CONFIG_FILE_NAME; + if (args.length > 0) { + configFileName = args[0]; + } + CoreSettings coreSettings = loadCoreSettings(configFileName); + FrostMqttServer server = new FrostMqttServer(coreSettings); + server.start(); + + boolean waitForEnter = coreSettings.getMqttSettings().getCustomSettings().getBoolean(KEY_WAIT_FOR_ENTER, false); + if (waitForEnter) { + try (BufferedReader input = new BufferedReader(new InputStreamReader(System.in, StringHelper.ENCODING))) { + LOGGER.warn("Press Enter to exit."); + String read = input.readLine(); + LOGGER.warn("Exiting due to input {}...", read); + server.stop(); + System.exit(0); + } + } + } + +} diff --git a/FROST-Server.MQTT/src/main/resources/FrostMqtt.properties b/FROST-Server.MQTT/src/main/resources/FrostMqtt.properties new file mode 100644 index 000000000..fb10d3e06 --- /dev/null +++ b/FROST-Server.MQTT/src/main/resources/FrostMqtt.properties @@ -0,0 +1,26 @@ +# Base settings +ApiVersion=v1.0 +serviceRootUrl=http://localhost:8080/FROST-Server +# MQTT settings +mqtt.mqttServerImplementationClass=de.fraunhofer.iosb.ilt.sensorthingsserver.mqtt.moquette.MoquetteMqttServer +mqtt.Enabled=true +mqtt.Port=1883 +mqtt.QoS=1 +mqtt.SubscribeMessageQueueSize=100 +mqtt.SubscribeThreadPoolSize=20 +mqtt.CreateMessageQueueSize=100 +mqtt.CreateThreadPoolSize=10 +mqtt.Host=0.0.0.0 +mqtt.internalHost=localhost +mqtt.WebsocketPort=9876 +mqtt.WaitForEnter=true +# bus settings +bus.busImplementationClass=de.fraunhofer.iosb.ilt.sta.messagebus.MqttMessageBus +bus.mqttBroker=tcp://127.0.0.1:1884 +# persistence settings +persistence.persistenceManagerImplementationClass=de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong +persistence.alwaysOrderbyId=false +persistence.db.driver=org.postgresql.Driver +persistence.db.url=jdbc:postgresql://database:5432/sensorthings +persistence.db.username=sensorthings +persistence.db.password=ChangeMe diff --git a/FROST-Server.MQTT/src/main/resources/logback.xml b/FROST-Server.MQTT/src/main/resources/logback.xml new file mode 100644 index 000000000..58407dbac --- /dev/null +++ b/FROST-Server.MQTT/src/main/resources/logback.xml @@ -0,0 +1,41 @@ + + + + + %d{HH:mm:ss.SSS} [%16thread] %-5level %30logger{30} - %msg%n + + + + + ../logs/SensorThingsService.log + + + ../logs/SensorThingsService_%d{yyyy-MM-dd}.%i.log + + + 50MB + + + 30 + + + + UTF-8 + %d %-4relative [%thread] %-5level %logger{35} - %msg%n + + + + + + + + + + + + + + + + diff --git a/FROST-Server.MQTTP/Dockerfile b/FROST-Server.MQTTP/Dockerfile new file mode 100644 index 000000000..e060bfc30 --- /dev/null +++ b/FROST-Server.MQTTP/Dockerfile @@ -0,0 +1,8 @@ +From tomcat:8-jre8 + +ADD http://repo.maven.apache.org/maven2/org/postgresql/postgresql/9.4.1212/postgresql-9.4.1212.jar /usr/local/tomcat/lib/ +ADD http://repo.maven.apache.org/maven2/net/postgis/postgis-jdbc/2.2.1/postgis-jdbc-2.2.1.jar /usr/local/tomcat/lib/ + +# Copy to images tomcat path +ARG WAR_FILE +ADD target/${WAR_FILE} /usr/local/tomcat/webapps/FROST-Server.war diff --git a/FROST-Server.MQTTP/nb-configuration.xml b/FROST-Server.MQTTP/nb-configuration.xml new file mode 100644 index 000000000..b7722cb1c --- /dev/null +++ b/FROST-Server.MQTTP/nb-configuration.xml @@ -0,0 +1,34 @@ + + + + + + 1.7-web + js/libs + Tomcat + none + 4 + 4 + 4 + 80 + true + none + 4 + 4 + 8 + 80 + true + project + false + + diff --git a/FROST-Server.MQTTP/pom.xml b/FROST-Server.MQTTP/pom.xml new file mode 100644 index 000000000..f5eeb6fa6 --- /dev/null +++ b/FROST-Server.MQTTP/pom.xml @@ -0,0 +1,176 @@ + + + 4.0.0 + + de.fraunhofer.iosb.ilt.FROST-Server + FROST-ServerParent + 1.9-SNAPSHOT + ../pom.xml + + FROST-Server.MQTTP + war + + FROST-Server.MQTTP + The web-application making the FROST-Server available over HTTP and MQTT. + https://github.com/FraunhoferIOSB/FROST-Server + + + UTF-8 + fraunhoferiosb/frost-server + + + + + ${project.groupId} + FROST-Server.Core + ${project.version} + + + ${project.groupId} + FROST-Server.HTTP.Common + ${project.version} + + + ${project.groupId} + FROST-Server.SQL + ${project.version} + + + ${project.groupId} + FROST-Server.SQL.PGLong + ${project.version} + + + ${project.groupId} + FROST-Server.SQL.PGString + ${project.version} + + + ${project.groupId} + FROST-Server.SQL.PGUuid + ${project.version} + + + ${project.groupId} + FROST-Server.MQTT.Moquette + ${project.version} + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + javax + javaee-web-api + 7.0 + provided + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.source} + ${maven.compiler.target} + + ${endorsed.dir} + + + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + validate + + copy + + + ${endorsed.dir} + true + + + javax + javaee-endorsed-api + 7.0 + jar + + + + + + + + com.spotify + dockerfile-maven-plugin + ${dockerfile-maven-plugin.version} + + + build-and-tag-latest + none + + build + + + + tag-version + none + + tag + + + ${env.TAG} + + + + push-latest + none + + push + + + latest + + + + push-version + none + + push + + + ${env.TAG} + + + + + ${docker-image-name} + + ${project.build.finalName}.war + + + + + + javax.activation + activation + ${javax-activation.version} + + + + + + + diff --git a/FROST-Server.MQTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/ContextListener.java b/FROST-Server.MQTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/ContextListener.java new file mode 100644 index 000000000..5d702c5cb --- /dev/null +++ b/FROST-Server.MQTTP/src/main/java/de/fraunhofer/iosb/ilt/sta/ContextListener.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 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.sta; + +import de.fraunhofer.iosb.ilt.frostserver.http.common.AbstractContextListener; +import de.fraunhofer.iosb.ilt.sta.messagebus.MessageBusFactory; +import de.fraunhofer.iosb.ilt.sta.mqtt.MqttManager; +import javax.servlet.ServletContextEvent; +import javax.servlet.annotation.WebListener; + +/** + * @author jab, scf + */ +@WebListener +public class ContextListener extends AbstractContextListener { + + @Override + public void contextInitialized(ServletContextEvent sce) { + super.contextInitialized(sce); + + if (sce != null && sce.getServletContext() != null) { + MqttManager.init(getCoreSettings()); + MessageBusFactory.getMessageBus().addMessageListener(MqttManager.getInstance()); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + MqttManager.shutdown(); + super.contextDestroyed(sce); + } + +} diff --git a/FROST-Server.MQTTP/src/main/resources/logback.xml b/FROST-Server.MQTTP/src/main/resources/logback.xml new file mode 100644 index 000000000..928838deb --- /dev/null +++ b/FROST-Server.MQTTP/src/main/resources/logback.xml @@ -0,0 +1,41 @@ + + + + + %d{HH:mm:ss.SSS} [%16thread] %-5level %30logger{30} - %msg%n + + + + + ../logs/SensorThingsService.log + + + ../logs/SensorThingsService_%d{yyyy-MM-dd}.%i.log + + + 50MB + + + 30 + + + + UTF-8 + %d %-4relative [%thread] %-5level %logger{35} - %msg%n + + + + + + + + + + + + + + + + diff --git a/FROST-Server.MQTTP/src/main/webapp/META-INF/context.xml b/FROST-Server.MQTTP/src/main/webapp/META-INF/context.xml new file mode 100644 index 000000000..711de4225 --- /dev/null +++ b/FROST-Server.MQTTP/src/main/webapp/META-INF/context.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FROST-Server.MQTTP/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/FROST-Server.MQTTP/src/main/webapp/WEB-INF/jboss-deployment-structure.xml new file mode 100644 index 000000000..220ecd17d --- /dev/null +++ b/FROST-Server.MQTTP/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/FROST-Server.MQTTP/src/main/webapp/WEB-INF/web.xml b/FROST-Server.MQTTP/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..c8b9b2834 --- /dev/null +++ b/FROST-Server.MQTTP/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,156 @@ + + + + + + + + The version tag of the API used in the URL. + ApiVersion + v1.0 + + + The base URL of the SensorThings Server without version. + serviceRootUrl + http://localhost:8080/FROST-Server + + + The default value for the $count query option. + defaultCount + false + + + The default value for the $top query option. + defaultTop + 100 + + + The maximum allowed value for the $top query option. + maxTop + 10000 + + + The maximum allowed estimated data size (in bytes) for responses. + maxDataSize + 25000000 + + + If true, navigationLinks are absolute, otherwise relative. + useAbsoluteNavigationLinks + true + + + The java class used for running the MQTT server (must implement MqttServer interface) + mqtt.mqttServerImplementationClass + de.fraunhofer.iosb.ilt.sensorthingsserver.mqtt.moquette.MoquetteMqttServer + + + Specifies wether MQTT support will be enabled or not. + mqtt.Enabled + true + + + The port the MQTT server runs on. + mqtt.Port + 1883 + + + Quality of Service Level for MQTT messages. + mqtt.QoS + 0 + + + Queue size for messages to be pubslihed via MQTT. + mqtt.SubscribeMessageQueueSize + 100 + + + Number of threads use to dispatch MQTT notifications. + mqtt.SubscribeThreadPoolSize + 20 + + + Queue size for create observation requests via MQTT. + mqtt.CreateMessageQueueSize + 100 + + + Number of threads use to dispatch observation creation requests. + mqtt.CreateThreadPoolSize + 10 + + + The external IP address or host name the MQTT server should listen on. Set to 0.0.0.0 to listen on all interfaces. + mqtt.Host + 0.0.0.0 + + + The internal host name of the MQTT server. + mqtt.internalHost + localhost + + + + The port the MQTT server is reachable via WebSocket. + mqtt.WebsocketPort + 9876 + + + + The java class used for persistence (must implement PersistenceManaher interface) + persistence.persistenceManagerImplementationClass + de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong + + + + Automatically apply database updates. + persistence.autoUpdateDatabase + false + + + Always add an 'orderby=id asc' to queries to ensure consistent paging. + persistence.alwaysOrderbyId + false + + + + JNDI data source name + persistence.db_jndi_datasource + jdbc/sensorThings + + diff --git a/FROST-Server.MQTTP/src/main/webapp/index.html b/FROST-Server.MQTTP/src/main/webapp/index.html new file mode 100644 index 000000000..d229db5c5 --- /dev/null +++ b/FROST-Server.MQTTP/src/main/webapp/index.html @@ -0,0 +1,38 @@ + + + + Start Page + + + + +

FROST-Server

+ The Fraunhofer Open-source SensorThings Server. + +

Some Links

+ Database Status and Update
+ SensorThingsApi v1.0
+ FROST-Server on GitHub + + + diff --git a/FROST-Server.SQL.PGLong/pom.xml b/FROST-Server.SQL.PGLong/pom.xml index 52d29fb23..48c8f0671 100644 --- a/FROST-Server.SQL.PGLong/pom.xml +++ b/FROST-Server.SQL.PGLong/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT ../pom.xml FROST-Server.SQL.PGLong @@ -52,11 +52,6 @@ ${postgis.version} provided
- - com.h2database - h2 - ${h2.version} - org.slf4j slf4j-api @@ -156,88 +151,10 @@ - - org.liquibase - liquibase-maven-plugin - ${liquibase.version} - - ${project.basedir}/src/main/resources/liquibase/tables.xml - ${builddatabase.driver} - ${builddatabase.url} - ${builddatabase.username} - ${builddatabase.password} - false - - - - generate-sources - - update - - - - - - com.querydsl - querydsl-maven-plugin - ${querydsl.version} - - - - export - - - - - ${builddatabase.driver} - ${builddatabase.url} - ${builddatabase.username} - ${builddatabase.password} - - LOCATIONS_HIST_LOCATIONS,HIST_LOCATIONS,THINGS_LOCATIONS,LOCATIONS,OBSERVATIONS,FEATURES,DATASTREAMS,MULTI_DATASTREAMS,OBS_PROPERTIES,SENSORS,THINGS,MULTI_DATASTREAMS_OBS_PROPERTIES, - locations_hist_locations,hist_locations,things_locations,locations,observations,features,datastreams,multi_datastreams,obs_properties,sensors,things,multi_datastreams_obs_properties - - de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid - ${project.basedir}/target/generated-sources/java - true - - - - javax.annotation - javax.annotation-api - ${annotation-api.version} - - - com.mysema.codegen - codegen - ${codegen.version} - - - com.h2database - h2 - ${h2.version} - - - org.postgresql - postgresql - ${postgres.version} - - - net.postgis - postgis-jdbc - ${postgis.version} - - - ch.qos.logback - logback-classic - 1.1.3 - - - org.apache.maven.plugins maven-compiler-plugin - 3.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/EntityCreator.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/EntityCreator.java deleted file mode 100644 index a93d0b452..000000000 --- a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/EntityCreator.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.longid; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.mysema.commons.lang.CloseableIterator; -import com.querydsl.core.Tuple; -import com.querydsl.sql.SQLQuery; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.NavigableElement; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyArrayIndex; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; -import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathVisitor; -import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; -import de.fraunhofer.iosb.ilt.sta.query.Expand; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - */ -class EntityCreator implements ResourcePathVisitor { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(EntityCreator.class); - private final PersistenceManager pm; - private final ResourcePath path; - private final Query query; - private final SQLQuery sqlQuery; - private Object resultObject; - /** - * If resultObject is a property or sub-property, and we are not using - * $value, then the resultObject is encapsulated in a Map, using this key. - */ - private String entityName; - - /** - * - * @param pm The persistence manager. - * @param path The path leading to the items. - * @param query The query parameters to use when fetching expanded items. - * @param sqlQuery The sql query to use for fetching items. - */ - public EntityCreator(PersistenceManager pm, ResourcePath path, Query query, SQLQuery sqlQuery) { - this.pm = pm; - this.path = path; - this.query = query; - this.sqlQuery = sqlQuery; - } - - public Object getEntity() { - return resultObject; - } - - /** - * If resultObject is a property or sub-property, and we are not using - * $value, then the resultObject is encapsulated in a Map, using this key. - * - * @return The name of the resultObject in the map. - */ - public String getEntityName() { - return entityName; - } - - @Override - public void visit(EntityPathElement element) { - sqlQuery.limit(2); - List results = sqlQuery.fetch(); - if (results.size() > 1) { - throw new IllegalStateException("Expecting an element, yet more than 1 result. Got " + results.size() + " results."); - } - if (results.isEmpty()) { - return; - } - - PropertyHelper.entityFromTupleFactory factory = PropertyHelper.getFactoryFor(element.getEntityType().getImplementingClass()); - Entity entity = factory.create(results.get(0), query, new DataSize()); - - if (entity == null) { - throw new IllegalStateException("Failed to create an entity from result set."); - } - expandEntity(entity, query); - resultObject = entity; - } - - private void expandEntity(Entity e, Query query) { - if (query == null) { - return; - } - for (Expand expand : query.getExpand()) { - ResourcePath ePath = new ResourcePath(path.getServiceRootUrl(), null); - ResourcePathElement parentCollection = new EntitySetPathElement(e.getEntityType(), null); - ePath.addPathElement(parentCollection, false, false); - ResourcePathElement parent = new EntityPathElement(e.getId(), e.getEntityType(), parentCollection); - ePath.addPathElement(parent, false, true); - - NavigationProperty firstNp = expand.getPath().get(0); - NavigableElement existing = null; - { - Object o = e.getProperty(firstNp); - if (o instanceof NavigableElement) { - existing = (NavigableElement) o; - } - } - - if (firstNp.isSet) { - EntitySetPathElement child = new EntitySetPathElement(firstNp.type, parent); - ePath.addPathElement(child, true, false); - } else { - EntityPathElement child = new EntityPathElement(null, firstNp.type, parent); - ePath.addPathElement(child, true, false); - } - - Object child; - Query subQuery; - if (expand.getPath().size() == 1) { - // This was the last element in the expand path. The query is for this element. - subQuery = expand.getSubQuery(); - if (subQuery == null) { - subQuery = new Query(query.getSettings()); - } - } else { - // This is not the last element in the expand path. The query is not for this element. - subQuery = new Query(query.getSettings()); - Expand subExpand = new Expand(); - subExpand.getPath().addAll(expand.getPath()); - subExpand.getPath().remove(0); - subExpand.setSubQuery(expand.getSubQuery()); - subQuery.addExpand(subExpand); - if (query.getCount().isPresent()) { - subQuery.setCount(query.isCountOrDefault()); - } - } - - if (existing == null || !existing.isExportObject()) { - child = pm.get(ePath, subQuery); - e.setProperty(firstNp, child); - } else if (existing instanceof EntitySet) { - EntitySet entitySet = (EntitySet) existing; - for (Object subEntity : entitySet) { - if (subEntity instanceof Entity) { - Entity entity = (Entity) subEntity; - expandEntity(entity, subQuery); - } - } - } else if (existing instanceof Entity) { - Entity entity = (Entity) existing; - expandEntity(entity, subQuery); - } - } - } - - @Override - public void visit(EntitySetPathElement element) { - - int top = query.getTopOrDefault(); - sqlQuery.limit(top + 1); - - int skip = 0; - if (query.getSkip().isPresent()) { - skip = query.getSkip().get(); - sqlQuery.offset(skip); - } - long start = System.currentTimeMillis(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Query: {}", sqlQuery.getSQL().getSQL()); - } - CloseableIterator results = sqlQuery.iterate(); //fetch(); - if (LOGGER.isDebugEnabled()) { - long end = System.currentTimeMillis(); - LOGGER.debug("Query executed in {} ms.", end - start); - } - - PropertyHelper.entityFromTupleFactory factory = PropertyHelper.getFactoryFor(element.getEntityType().getImplementingClass()); - EntitySet entitySet = PropertyHelper.createSetFromTuples(factory, results, query, pm.getCoreSettings().getDataSizeMax()); - - if (entitySet == null) { - throw new IllegalStateException("Empty set!"); - } - - if (query.isCountOrDefault()) { - SQLQuery countQuery = sqlQuery.clone(); - countQuery.select(factory.getPrimaryKey()); - int count = (int) countQuery.fetchCount(); - entitySet.setCount(count); - } - - int entityCount = entitySet.size(); - boolean hasMore = results.hasNext(); - if (entityCount < top && hasMore) { - // The loading was aborted, probably due to size constraints. - query.setTop(entityCount); - } - if (hasMore) { - entitySet.setNextLink(UrlHelper.generateNextLink(path, query)); - } - for (Entity e : entitySet) { - expandEntity(e, query); - } - resultObject = entitySet; - } - - @Override - public void visit(PropertyPathElement element) { - element.getParent().visit(this); - if (Entity.class.isAssignableFrom(resultObject.getClass())) { - Object propertyValue = ((Entity) resultObject).getProperty(element.getProperty()); - Map entityMap = new HashMap<>(); - entityName = element.getProperty().name; - entityMap.put(entityName, propertyValue); - resultObject = entityMap; - } - } - - @Override - public void visit(CustomPropertyPathElement element) { - element.getParent().visit(this); - String name = element.getName(); - if (resultObject instanceof Map) { - Map map = (Map) resultObject; - Object inner = map.get(entityName); - if (inner instanceof Map) { - map = (Map) inner; - if (map.containsKey(name)) { - Object propertyValue = map.get(name); - Map entityMap = new HashMap<>(); - entityName = name; - entityMap.put(entityName, propertyValue); - resultObject = entityMap; - return; - } - } - } - - resultObject = null; - entityName = null; - } - - @Override - public void visit(CustomPropertyArrayIndex element) { - element.getParent().visit(this); - int index = element.getIndex(); - if (resultObject instanceof Map) { - Map map = (Map) resultObject; - Object inner = map.get(entityName); - Object propertyValue = null; - if (inner instanceof ArrayNode && ((ArrayNode) inner).size() > index) { - propertyValue = ((ArrayNode) inner).get(index); - } - if (inner instanceof List && ((List) inner).size() > index) { - propertyValue = ((List) inner).get(index); - } - if (propertyValue != null) { - Map entityMap = new HashMap<>(); - entityName = entityName + "[" + Integer.toString(index) + "]"; - entityMap.put(entityName, propertyValue); - resultObject = entityMap; - return; - } - } - - resultObject = null; - entityName = null; - } - -} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/EntityInserter.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/EntityInserter.java deleted file mode 100644 index 05c4dc2de..000000000 --- a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/EntityInserter.java +++ /dev/null @@ -1,1593 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.longid; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.querydsl.core.Tuple; -import com.querydsl.core.dml.StoreClause; -import com.querydsl.core.types.dsl.DateTimePath; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.StringPath; -import com.querydsl.spatial.GeometryPath; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import com.querydsl.sql.dml.SQLInsertClause; -import com.querydsl.sql.dml.SQLUpdateClause; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.geojson.GeoJsonDeserializier; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; -import de.fraunhofer.iosb.ilt.sta.model.builder.DatastreamBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.FeatureOfInterestBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.MultiDatastreamBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.SensorBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.custom.geojson.GeoJsonSerializer; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.ResultType; -import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; -import java.io.IOException; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import org.geojson.Crs; -import org.geojson.Feature; -import org.geojson.GeoJsonObject; -import org.geojson.jackson.CrsType; -import org.geolatte.common.dataformats.json.jackson.JsonException; -import org.geolatte.common.dataformats.json.jackson.JsonMapper; -import org.geolatte.geom.Geometry; -import org.joda.time.Interval; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author Hylke van der Schaaf - */ -public class EntityInserter { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(EntityInserter.class); - private final PostgresPersistenceManagerLong pm; - private ObjectMapper formatter; - - public EntityInserter(PostgresPersistenceManagerLong pm) { - this.pm = pm; - } - - public boolean insertDatastream(Datastream ds) throws NoSuchEntityException, IncompleteEntityException { - // First check ObservedPropery, Sensor and Thing - ObservedProperty op = ds.getObservedProperty(); - entityExistsOrCreate(op); - - Sensor s = ds.getSensor(); - entityExistsOrCreate(s); - - Thing t = ds.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - - QDatastreams qd = QDatastreams.datastreams; - SQLInsertClause insert = qFactory.insert(qd); - insert.set(qd.name, ds.getName()); - insert.set(qd.description, ds.getDescription()); - insert.set(qd.observationType, ds.getObservationType()); - insert.set(qd.unitDefinition, ds.getUnitOfMeasurement().getDefinition()); - insert.set(qd.unitName, ds.getUnitOfMeasurement().getName()); - insert.set(qd.unitSymbol, ds.getUnitOfMeasurement().getSymbol()); - insert.set(qd.properties, objectToJson(ds.getProperties())); - - insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MAX.getMillis())); - insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MIN.getMillis())); - insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MAX.getMillis())); - insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MIN.getMillis())); - - insert.set(qd.obsPropertyId, (Long) op.getId().getValue()); - insert.set(qd.sensorId, (Long) s.getId().getValue()); - insert.set(qd.thingId, (Long) t.getId().getValue()); - - Long datastreamId = insert.executeWithKey(qd.id); - LOGGER.info("Inserted datastream. Created id = {}.", datastreamId); - ds.setId(new LongId(datastreamId)); - - // Create Observations, if any. - for (Observation o : ds.getObservations()) { - o.setDatastream(new DatastreamBuilder().setId(ds.getId()).build()); - o.complete(); - pm.insert(o); - } - - return true; - } - - public boolean updateDatastream(Datastream d, long dsId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QDatastreams qd = QDatastreams.datastreams; - SQLUpdateClause update = qFactory.update(qd); - if (d.isSetName()) { - if (d.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qd.name, d.getName()); - } - if (d.isSetDescription()) { - if (d.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qd.description, d.getDescription()); - } - if (d.isSetObservationType()) { - if (d.getObservationType() == null) { - throw new IncompleteEntityException("observationType can not be null."); - } - update.set(qd.observationType, d.getObservationType()); - } - if (d.isSetProperties()) { - update.set(qd.properties, objectToJson(d.getProperties())); - } - if (d.isSetObservedProperty()) { - if (!entityExists(d.getObservedProperty())) { - throw new NoSuchEntityException("ObservedProperty with no id or not found."); - } - update.set(qd.obsPropertyId, (Long) d.getObservedProperty().getId().getValue()); - } - if (d.isSetSensor()) { - if (!entityExists(d.getSensor())) { - throw new NoSuchEntityException("Sensor with no id or not found."); - } - update.set(qd.sensorId, (Long) d.getSensor().getId().getValue()); - } - if (d.isSetThing()) { - if (!entityExists(d.getThing())) { - throw new NoSuchEntityException("Thing with no id or not found."); - } - update.set(qd.thingId, (Long) d.getThing().getId().getValue()); - } - if (d.isSetUnitOfMeasurement()) { - if (d.getUnitOfMeasurement() == null) { - throw new IncompleteEntityException("unitOfMeasurement can not be null."); - } - UnitOfMeasurement uom = d.getUnitOfMeasurement(); - update.set(qd.unitDefinition, uom.getDefinition()); - update.set(qd.unitName, uom.getName()); - update.set(qd.unitSymbol, uom.getSymbol()); - } - - update.where(qd.id.eq(dsId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Datastream {} caused {} rows to change!", dsId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Observations to the Datastream. - for (Observation o : d.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - Long obsId = (Long) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.datastreamId, dsId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to Observation {}.", dsId, obsId); - } - } - - LOGGER.debug("Updated Datastream {}", dsId); - return true; - } - - public boolean insertMultiDatastream(MultiDatastream ds) throws NoSuchEntityException, IncompleteEntityException { - // First check Sensor and Thing - Sensor s = ds.getSensor(); - entityExistsOrCreate(s); - - Thing t = ds.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - - QMultiDatastreams qd = QMultiDatastreams.multiDatastreams; - SQLInsertClause insert = qFactory.insert(qd); - insert.set(qd.name, ds.getName()); - insert.set(qd.description, ds.getDescription()); - insert.set(qd.observationTypes, objectToJson(ds.getMultiObservationDataTypes())); - insert.set(qd.unitOfMeasurements, objectToJson(ds.getUnitOfMeasurements())); - insert.set(qd.properties, objectToJson(ds.getProperties())); - - insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MAX.getMillis())); - insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MIN.getMillis())); - insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MAX.getMillis())); - insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManagerLong.DATETIME_MIN.getMillis())); - - insert.set(qd.sensorId, (Long) s.getId().getValue()); - insert.set(qd.thingId, (Long) t.getId().getValue()); - - Long multiDatastreamId = insert.executeWithKey(qd.id); - LOGGER.info("Inserted multiDatastream. Created id = {}.", multiDatastreamId); - ds.setId(new LongId(multiDatastreamId)); - - // Create new Locations, if any. - EntitySet ops = ds.getObservedProperties(); - int rank = 0; - for (ObservedProperty op : ops) { - entityExistsOrCreate(op); - Long opId = (Long) op.getId().getValue(); - - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - insert = qFactory.insert(qMdOp); - insert.set(qMdOp.multiDatastreamId, multiDatastreamId); - insert.set(qMdOp.obsPropertyId, opId); - insert.set(qMdOp.rank, rank); - insert.execute(); - LOGGER.debug("Linked MultiDatastream {} to ObservedProperty {} with rank {}.", multiDatastreamId, opId, rank); - rank++; - } - - // Create Observations, if any. - for (Observation o : ds.getObservations()) { - o.setMultiDatastream(new MultiDatastreamBuilder().setId(ds.getId()).build()); - o.complete(); - pm.insert(o); - } - - return true; - } - - public boolean updateMultiDatastream(MultiDatastream d, long dsId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QMultiDatastreams qd = QMultiDatastreams.multiDatastreams; - SQLUpdateClause update = qFactory.update(qd); - if (d.isSetName()) { - if (d.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qd.name, d.getName()); - } - if (d.isSetDescription()) { - if (d.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qd.description, d.getDescription()); - } - if (d.isSetProperties()) { - update.set(qd.properties, objectToJson(d.getProperties())); - } - - if (d.isSetSensor()) { - if (!entityExists(d.getSensor())) { - throw new NoSuchEntityException("Sensor with no id or not found."); - } - update.set(qd.sensorId, (Long) d.getSensor().getId().getValue()); - } - if (d.isSetThing()) { - if (!entityExists(d.getThing())) { - throw new NoSuchEntityException("Thing with no id or not found."); - } - update.set(qd.thingId, (Long) d.getThing().getId().getValue()); - } - - MultiDatastream original = (MultiDatastream) pm.getEntityById(null, EntityType.MultiDatastream, new LongId(dsId)); - int countOrig = original.getMultiObservationDataTypes().size(); - - int countUom = countOrig; - if (d.isSetUnitOfMeasurements()) { - if (d.getUnitOfMeasurements() == null) { - throw new IncompleteEntityException("unitOfMeasurements can not be null."); - } - List uoms = d.getUnitOfMeasurements(); - countUom = uoms.size(); - update.set(qd.unitOfMeasurements, objectToJson(uoms)); - } - int countDataTypes = countOrig; - if (d.isSetMultiObservationDataTypes()) { - List dataTypes = d.getMultiObservationDataTypes(); - if (dataTypes == null) { - throw new IncompleteEntityException("multiObservationDataTypes can not be null."); - } - countDataTypes = dataTypes.size(); - update.set(qd.observationTypes, objectToJson(dataTypes)); - } - EntitySet ops = d.getObservedProperties(); - int countOps = countOrig + ops.size(); - for (ObservedProperty op : ops) { - if (op.getId() == null || !entityExists(op)) { - throw new NoSuchEntityException("ObservedProperty with no id or not found."); - } - } - - if (countUom != countDataTypes) { - throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of multiObservationDataTypes."); - } - if (countUom != countOps) { - throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of ObservedProperties."); - } - - update.where(qd.id.eq(dsId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Datastream {} caused {} rows to change!", dsId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing ObservedProperties to the MultiDatastream. - int rank = countOrig; - for (ObservedProperty op : ops) { - Long opId = (Long) op.getId().getValue(); - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - long oCount = qFactory.insert(qMdOp) - .set(qMdOp.multiDatastreamId, dsId) - .set(qMdOp.obsPropertyId, opId) - .set(qMdOp.rank, rank) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to ObservedProperty {} with rank {}.", dsId, opId, rank); - } - rank++; - } - - // Link existing Observations to the MultiDatastream. - for (Observation o : d.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - Long obsId = (Long) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.datastreamId, dsId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to Observation {}.", dsId, obsId); - } - } - - LOGGER.debug("Updated Datastream {}", dsId); - return true; - } - - public boolean insertFeatureOfInterest(FeatureOfInterest foi) throws NoSuchEntityException { - // No linked entities to check first. - SQLQueryFactory qFactory = pm.createQueryFactory(); - QFeatures qfoi = QFeatures.features; - SQLInsertClause insert = qFactory.insert(qfoi); - insert.set(qfoi.name, foi.getName()); - insert.set(qfoi.description, foi.getDescription()); - insert.set(qfoi.properties, objectToJson(foi.getProperties())); - - String encodingType = foi.getEncodingType(); - insert.set(qfoi.encodingType, encodingType); - insertGeometry(insert, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); - - Long generatedId = insert.executeWithKey(qfoi.id); - LOGGER.info("Inserted FeatureOfInterest. Created id = {}.", generatedId); - foi.setId(new LongId(generatedId)); - return true; - } - - public boolean updateFeatureOfInterest(FeatureOfInterest foi, long foiId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QFeatures qfoi = QFeatures.features; - SQLUpdateClause update = qFactory.update(qfoi); - if (foi.isSetName()) { - if (foi.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qfoi.name, foi.getName()); - } - if (foi.isSetDescription()) { - if (foi.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qfoi.description, foi.getDescription()); - } - if (foi.isSetProperties()) { - update.set(qfoi.properties, objectToJson(foi.getProperties())); - } - - if (foi.isSetEncodingType() && foi.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - if (foi.isSetFeature() && foi.getFeature() == null) { - throw new IncompleteEntityException("feature can not be null."); - } - if (foi.isSetEncodingType() && foi.getEncodingType() != null && foi.isSetFeature() && foi.getFeature() != null) { - String encodingType = foi.getEncodingType(); - update.set(qfoi.encodingType, encodingType); - insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); - } else if (foi.isSetEncodingType() && foi.getEncodingType() != null) { - String encodingType = foi.getEncodingType(); - update.set(qfoi.encodingType, encodingType); - } else if (foi.isSetFeature() && foi.getFeature() != null) { - String encodingType = qFactory.select(qfoi.encodingType) - .from(qfoi) - .where(qfoi.id.eq(foiId)) - .fetchFirst(); - Object parsedObject = reParseGeometry(encodingType, foi.getFeature()); - insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, parsedObject); - } - - update.where(qfoi.id.eq(foiId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating FeatureOfInterest {} caused {} rows to change!", foiId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Observations to the FeatureOfInterest. - for (Observation o : foi.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - Long obsId = (Long) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.featureId, foiId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned FeatureOfInterest {} to Observation {}.", foiId, obsId); - } - } - - LOGGER.debug("Updated FeatureOfInterest {}", foiId); - return true; - } - - public FeatureOfInterest generateFeatureOfInterest(Id datastreamId, boolean isMultiDatastream) throws NoSuchEntityException { - Long dsId = (Long) datastreamId.getValue(); - SQLQueryFactory qf = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - QThingsLocations qtl = QThingsLocations.thingsLocations; - QThings qt = QThings.things; - QDatastreams qd = QDatastreams.datastreams; - QMultiDatastreams qmd = QMultiDatastreams.multiDatastreams; - // TODO: Should probably contain a where that only returns locations - // with a supported encoding type. - SQLQuery query = qf.select(ql.id, ql.genFoiId) - .from(ql) - .innerJoin(qtl).on(ql.id.eq(qtl.locationId)) - .innerJoin(qt).on(qt.id.eq(qtl.thingId)); - if (isMultiDatastream) { - query.innerJoin(qmd).on(qmd.thingId.eq(qt.id)) - .where(qmd.id.eq(dsId)); - } else { - query.innerJoin(qd).on(qd.thingId.eq(qt.id)) - .where(qd.id.eq(dsId)); - } - Tuple tuple = query.fetchOne(); - if (tuple == null) { - // Can not generate foi from Thing with no locations. - throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); - } - Long genFoiId = tuple.get(ql.genFoiId); - Long locationId = tuple.get(ql.id); - - FeatureOfInterest foi; - if (genFoiId == null) { - query = qf.select(ql.id, ql.encodingType, ql.location) - .from(ql) - .where(ql.id.eq(locationId)); - tuple = query.fetchOne(); - if (tuple == null) { - // Can not generate foi from Thing with no locations. - // Should not happen, since the query succeeded just before. - throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); - } - String encoding = tuple.get(ql.encodingType); - String locString = tuple.get(ql.location); - Object locObject = PropertyHelper.locationFromEncoding(encoding, locString); - foi = new FeatureOfInterestBuilder() - .setName("FoI for location " + locationId) - .setDescription("Generated from location " + locationId) - .setEncodingType(encoding) - .setFeature(locObject) - .build(); - insertFeatureOfInterest(foi); - Long foiId = (Long) foi.getId().getValue(); - qf.update(ql) - .set(ql.genFoiId, (Long) foi.getId().getValue()) - .where(ql.id.eq(locationId)) - .execute(); - LOGGER.debug("Generated foi {} from Location {}.", foiId, locationId); - } else { - foi = new FeatureOfInterest(); - foi.setId(new LongId(genFoiId)); - } - return foi; - } - - public boolean insertHistoricalLocation(HistoricalLocation h) throws NoSuchEntityException, IncompleteEntityException { - Thing t = h.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QHistLocations qhl = QHistLocations.histLocations; - SQLInsertClause insert = qFactory.insert(qhl); - insert.set(qhl.time, new Timestamp(h.getTime().getDateTime().getMillis())); - insert.set(qhl.thingId, (Long) h.getThing().getId().getValue()); - - Long generatedId = insert.executeWithKey(qhl.id); - LOGGER.info("Inserted HistoricalLocation. Created id = {}.", generatedId); - h.setId(new LongId(generatedId)); - - EntitySet locations = h.getLocations(); - for (Location l : locations) { - entityExistsOrCreate(l); - Long lId = (Long) l.getId().getValue(); - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, generatedId); - insert.set(qlhl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", lId, generatedId); - } - return true; - } - - public boolean updateHistoricalLocation(HistoricalLocation hl, long id) { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QHistLocations qhl = QHistLocations.histLocations; - SQLUpdateClause update = qFactory.update(qhl); - if (hl.isSetThing()) { - if (!entityExists(hl.getThing())) { - throw new IncompleteEntityException("Thing can not be null."); - } - update.set(qhl.thingId, (Long) hl.getThing().getId().getValue()); - } - if (hl.isSetTime()) { - if (hl.getTime() == null) { - throw new IncompleteEntityException("time can not be null."); - } - insertTimeInstant(update, qhl.time, hl.getTime()); - } - update.where(qhl.id.eq(id)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Location {} caused {} rows to change!", id, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Location {}", id); - - // Link existing locations to the HistoricalLocation. - for (Location l : hl.getLocations()) { - if (!entityExists(l)) { - throw new IllegalArgumentException("Unknown Location or Location with no id."); - } - Long lId = (Long) l.getId().getValue(); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - SQLInsertClause insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, id); - insert.set(qlhl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", lId, id); - } - return true; - } - - public boolean insertLocation(Location l) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - SQLInsertClause insert = qFactory.insert(ql); - insert.set(ql.name, l.getName()); - insert.set(ql.description, l.getDescription()); - insert.set(ql.properties, objectToJson(l.getProperties())); - - String encodingType = l.getEncodingType(); - insert.set(ql.encodingType, encodingType); - insertGeometry(insert, ql.location, ql.geom, encodingType, l.getLocation()); - - Long locationId = insert.executeWithKey(ql.id); - LOGGER.info("Inserted Location. Created id = {}.", locationId); - l.setId(new LongId(locationId)); - - // Link Things - EntitySet things = l.getThings(); - for (Thing t : things) { - entityExistsOrCreate(t); - Long thingId = (Long) t.getId().getValue(); - - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - long count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new Location to thing. - insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - - // Create HistoricalLocation for Thing - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - Long histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - // Link Location to HistoricalLocation. - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locationId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locationId, histLocationId); - } - - return true; - } - - public boolean updateLocation(Location l, long locationId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - SQLUpdateClause update = qFactory.update(ql); - if (l.isSetName()) { - if (l.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(ql.name, l.getName()); - } - if (l.isSetDescription()) { - if (l.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(ql.description, l.getDescription()); - } - if (l.isSetProperties()) { - update.set(ql.properties, objectToJson(l.getProperties())); - } - - if (l.isSetEncodingType() && l.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - if (l.isSetLocation() && l.getLocation() == null) { - throw new IncompleteEntityException("locations can not be null."); - } - if (l.isSetEncodingType() && l.getEncodingType() != null && l.isSetLocation() && l.getLocation() != null) { - String encodingType = l.getEncodingType(); - update.set(ql.encodingType, encodingType); - insertGeometry(update, ql.location, ql.geom, encodingType, l.getLocation()); - } else if (l.isSetEncodingType() && l.getEncodingType() != null) { - String encodingType = l.getEncodingType(); - update.set(ql.encodingType, encodingType); - } else if (l.isSetLocation() && l.getLocation() != null) { - String encodingType = qFactory.select(ql.encodingType) - .from(ql) - .where(ql.id.eq(locationId)) - .fetchFirst(); - Object parsedObject = reParseGeometry(encodingType, l.getLocation()); - insertGeometry(update, ql.location, ql.geom, encodingType, parsedObject); - } - - update.where(ql.id.eq(locationId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Location {} caused {} rows to change!", locationId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Location {}", locationId); - - // Link HistoricalLocation. - for (HistoricalLocation hl : l.getHistoricalLocations()) { - if (hl.getId() == null) { - throw new IllegalArgumentException("HistoricalLocation with no id."); - } - Long hlId = (Long) hl.getId().getValue(); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - SQLInsertClause insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, hlId); - insert.set(qlhl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", locationId, hlId); - } - - // Link Things - EntitySet things = l.getThings(); - for (Thing t : things) { - if (!entityExists(t)) { - throw new NoSuchEntityException("Thing not found."); - } - Long thingId = (Long) t.getId().getValue(); - - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new Location to thing. - SQLInsertClause insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - - // Create HistoricalLocation for Thing - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - Long histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - // Link Location to HistoricalLocation. - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locationId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locationId, histLocationId); - } - return true; - } - - public boolean insertObservation(Observation o) throws NoSuchEntityException, IncompleteEntityException { - Datastream ds = o.getDatastream(); - MultiDatastream mds = o.getMultiDatastream(); - Id streamId; - boolean isMultiDatastream = false; - if (ds != null) { - entityExistsOrCreate(ds); - streamId = ds.getId(); - } else if (mds != null) { - entityExistsOrCreate(mds); - streamId = mds.getId(); - isMultiDatastream = true; - } else { - throw new IncompleteEntityException("Missing Datastream or MultiDatastream."); - } - - FeatureOfInterest f = o.getFeatureOfInterest(); - if (f == null) { - f = generateFeatureOfInterest(streamId, isMultiDatastream); - } else { - entityExistsOrCreate(f); - } - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObservations qo = QObservations.observations; - SQLInsertClause insert = qFactory.insert(qo); - - insert.set(qo.parameters, objectToJson(o.getParameters())); - TimeValue phenomenonTime = o.getPhenomenonTime(); - if (phenomenonTime == null) { - phenomenonTime = TimeInstant.now(); - } - insertTimeValue(insert, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, phenomenonTime); - insertTimeInstant(insert, qo.resultTime, o.getResultTime()); - insertTimeInterval(insert, qo.validTimeStart, qo.validTimeEnd, o.getValidTime()); - - Object result = o.getResult(); - if (isMultiDatastream) { - if (!(result instanceof List)) { - throw new IllegalArgumentException("Multidatastream only accepts array results."); - } - List list = (List) result; - ResourcePath path = mds.getPath(); - path.addPathElement(new EntitySetPathElement(EntityType.ObservedProperty, null), false, false); - long count = pm.count(path, null); - if (count != list.size()) { - throw new IllegalArgumentException("Size of result array (" + list.size() + ") must match number of observed properties (" + count + ") in the MultiDatastream."); - } - } - - if (result instanceof Number) { - insert.set(qo.resultType, ResultType.NUMBER.sqlValue()); - insert.set(qo.resultString, result.toString()); - insert.set(qo.resultNumber, ((Number) result).doubleValue()); - } else if (result instanceof Boolean) { - insert.set(qo.resultType, ResultType.BOOLEAN.sqlValue()); - insert.set(qo.resultString, result.toString()); - insert.set(qo.resultBoolean, (Boolean) result); - } else if (result instanceof String) { - insert.set(qo.resultType, ResultType.STRING.sqlValue()); - insert.set(qo.resultString, result.toString()); - } else { - insert.set(qo.resultType, ResultType.OBJECT_ARRAY.sqlValue()); - insert.set(qo.resultJson, objectToJson(result)); - } - - if (o.getResultQuality() != null) { - insert.set(qo.resultQuality, o.getResultQuality().toString()); - } - if (ds != null) { - insert.set(qo.datastreamId, (Long) ds.getId().getValue()); - } - if (mds != null) { - insert.set(qo.multiDatastreamId, (Long) mds.getId().getValue()); - } - insert.set(qo.featureId, (Long) f.getId().getValue()); - - Long generatedId = insert.executeWithKey(qo.id); - LOGGER.debug("Inserted Observation. Created id = {}.", generatedId); - o.setId(new LongId(generatedId)); - return true; - } - - public boolean updateObservation(Observation o, long id) { - Observation oldObservation = (Observation) pm.getEntityById(null, EntityType.Observation, new LongId(id)); - Datastream ds = oldObservation.getDatastream(); - MultiDatastream mds = oldObservation.getMultiDatastream(); - boolean newHasDatastream = ds != null; - boolean newHasMultiDatastream = mds != null; - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObservations qo = QObservations.observations; - SQLUpdateClause update = qFactory.update(qo); - if (o.isSetDatastream()) { - if (o.getDatastream() == null) { - newHasDatastream = false; - update.setNull(qo.datastreamId); - } else { - if (!entityExists(o.getDatastream())) { - throw new IncompleteEntityException("Datastream not found."); - } - newHasDatastream = true; - ds = o.getDatastream(); - update.set(qo.datastreamId, (Long) ds.getId().getValue()); - } - } - if (o.isSetMultiDatastream()) { - mds = o.getMultiDatastream(); - if (mds == null) { - newHasMultiDatastream = false; - update.setNull(qo.multiDatastreamId); - } else { - if (!entityExists(mds)) { - throw new IncompleteEntityException("MultiDatastream not found."); - } - newHasMultiDatastream = true; - update.set(qo.multiDatastreamId, (Long) mds.getId().getValue()); - } - } - if (newHasDatastream == newHasMultiDatastream) { - throw new IllegalArgumentException("Observation must have either a Datastream or a MultiDatastream."); - } - if (o.isSetFeatureOfInterest()) { - if (!entityExists(o.getFeatureOfInterest())) { - throw new IncompleteEntityException("FeatureOfInterest not found."); - } - update.set(qo.featureId, (Long) o.getFeatureOfInterest().getId().getValue()); - } - if (o.isSetParameters()) { - update.set(qo.parameters, objectToJson(o.getParameters())); - } - if (o.isSetPhenomenonTime()) { - if (o.getPhenomenonTime() == null) { - throw new IncompleteEntityException("phenomenonTime can not be null."); - } - insertTimeValue(update, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, o.getPhenomenonTime()); - } - - if (o.isSetResult() && o.getResult() != null) { - Object result = o.getResult(); - if (newHasMultiDatastream) { - if (!(result instanceof List)) { - throw new IllegalArgumentException("Multidatastream only accepts array results."); - } - List list = (List) result; - ResourcePath path = mds.getPath(); - path.addPathElement(new EntitySetPathElement(EntityType.ObservedProperty, null), false, false); - long count = pm.count(path, null); - if (count != list.size()) { - throw new IllegalArgumentException("Size of result array (" + list.size() + ") must match number of observed properties (" + count + ") in the MultiDatastream."); - } - } - if (result instanceof Number) { - update.set(qo.resultType, ResultType.NUMBER.sqlValue()); - update.set(qo.resultString, result.toString()); - update.set(qo.resultNumber, ((Number) result).doubleValue()); - update.setNull(qo.resultBoolean); - update.setNull(qo.resultJson); - } else if (result instanceof Boolean) { - update.set(qo.resultType, ResultType.BOOLEAN.sqlValue()); - update.set(qo.resultString, result.toString()); - update.set(qo.resultBoolean, (Boolean) result); - update.setNull(qo.resultNumber); - update.setNull(qo.resultJson); - } else if (result instanceof String) { - update.set(qo.resultType, ResultType.STRING.sqlValue()); - update.set(qo.resultString, result.toString()); - update.setNull(qo.resultNumber); - update.setNull(qo.resultBoolean); - update.setNull(qo.resultJson); - } else { - update.set(qo.resultType, ResultType.OBJECT_ARRAY.sqlValue()); - update.set(qo.resultJson, objectToJson(result)); - update.setNull(qo.resultString); - update.setNull(qo.resultNumber); - update.setNull(qo.resultBoolean); - } - } - - if (o.isSetResultQuality()) { - update.set(qo.resultQuality, objectToJson(o.getResultQuality())); - } - if (o.isSetResultTime()) { - insertTimeInstant(update, qo.resultTime, o.getResultTime()); - } - if (o.isSetValidTime()) { - insertTimeInterval(update, qo.validTimeStart, qo.validTimeEnd, o.getValidTime()); - } - update.where(qo.id.eq(id)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Observation {} caused {} rows to change!", id, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Observation {}", id); - return true; - } - - public boolean insertObservedProperty(ObservedProperty op) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObsProperties qop = QObsProperties.obsProperties; - SQLInsertClause insert = qFactory.insert(qop); - insert.set(qop.definition, op.getDefinition()); - insert.set(qop.name, op.getName()); - insert.set(qop.description, op.getDescription()); - insert.set(qop.properties, objectToJson(op.getProperties())); - - Long generatedId = insert.executeWithKey(qop.id); - LOGGER.info("Inserted ObservedProperty. Created id = {}.", generatedId); - op.setId(new LongId(generatedId)); - - // Create new datastreams, if any. - for (Datastream ds : op.getDatastreams()) { - ds.setSensor(new SensorBuilder().setId(op.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : op.getMultiDatastreams()) { - mds.setSensor(new SensorBuilder().setId(op.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - return true; - } - - public boolean updateObservedProperty(ObservedProperty op, long opId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObsProperties qop = QObsProperties.obsProperties; - SQLUpdateClause update = qFactory.update(qop); - if (op.isSetDefinition()) { - if (op.getDefinition() == null) { - throw new IncompleteEntityException("definition can not be null."); - } - update.set(qop.definition, op.getDefinition()); - } - if (op.isSetDescription()) { - if (op.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qop.description, op.getDescription()); - } - if (op.isSetName()) { - if (op.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qop.name, op.getName()); - } - if (op.isSetProperties()) { - update.set(qop.properties, objectToJson(op.getProperties())); - } - - update.where(qop.id.eq(opId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating ObservedProperty {} caused {} rows to change!", opId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Datastreams to the observedProperty. - for (Datastream ds : op.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("ObservedProperty with no id or non existing."); - } - Long dsId = (Long) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.obsPropertyId, opId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to ObservedProperty {}.", dsId, opId); - } - } - - if (!op.getMultiDatastreams().isEmpty()) { - throw new IllegalArgumentException("Can not add MultiDatastreams to an ObservedProperty."); - } - - LOGGER.debug("Updated ObservedProperty {}", opId); - return true; - } - - public boolean insertSensor(Sensor s) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QSensors qs = QSensors.sensors; - SQLInsertClause insert = qFactory.insert(qs); - insert.set(qs.name, s.getName()); - insert.set(qs.description, s.getDescription()); - insert.set(qs.encodingType, s.getEncodingType()); - // TODO: Check metadata serialisation. - insert.set(qs.metadata, s.getMetadata().toString()); - insert.set(qs.properties, objectToJson(s.getProperties())); - - Long generatedId = insert.executeWithKey(qs.id); - LOGGER.info("Inserted Sensor. Created id = {}.", generatedId); - s.setId(new LongId(generatedId)); - - // Create new datastreams, if any. - for (Datastream ds : s.getDatastreams()) { - ds.setSensor(new SensorBuilder().setId(s.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : s.getMultiDatastreams()) { - mds.setSensor(new SensorBuilder().setId(s.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - return true; - } - - public boolean updateSensor(Sensor s, long sensorId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QSensors qs = QSensors.sensors; - SQLUpdateClause update = qFactory.update(qs); - if (s.isSetName()) { - if (s.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qs.name, s.getName()); - } - if (s.isSetDescription()) { - if (s.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qs.description, s.getDescription()); - } - if (s.isSetEncodingType()) { - if (s.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - update.set(qs.encodingType, s.getEncodingType()); - } - if (s.isSetMetadata()) { - if (s.getMetadata() == null) { - throw new IncompleteEntityException("metadata can not be null."); - } - // TODO: Check metadata serialisation. - update.set(qs.metadata, s.getMetadata().toString()); - } - if (s.isSetProperties()) { - update.set(qs.properties, objectToJson(s.getProperties())); - } - - update.where(qs.id.eq(sensorId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Sensor {} caused {} rows to change!", sensorId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Datastreams to the sensor. - for (Datastream ds : s.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("Datastream with no id or non existing."); - } - Long dsId = (Long) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.sensorId, sensorId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to sensor {}.", dsId, sensorId); - } - } - - // Link existing MultiDatastreams to the sensor. - for (MultiDatastream mds : s.getMultiDatastreams()) { - if (mds.getId() == null || !entityExists(mds)) { - throw new NoSuchEntityException("MultiDatastream with no id or non existing."); - } - Long mdsId = (Long) mds.getId().getValue(); - QMultiDatastreams qmds = QMultiDatastreams.multiDatastreams; - long mdsCount = qFactory.update(qmds) - .set(qmds.sensorId, sensorId) - .where(qmds.id.eq(mdsId)) - .execute(); - if (mdsCount > 0) { - LOGGER.info("Assigned multiDatastream {} to sensor {}.", mdsId, sensorId); - } - } - - LOGGER.debug("Updated Sensor {}", sensorId); - return true; - } - - public boolean insertThing(Thing t) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QThings qt = QThings.things; - SQLInsertClause insert = qFactory.insert(qt); - insert.set(qt.name, t.getName()); - insert.set(qt.description, t.getDescription()); - insert.set(qt.properties, objectToJson(t.getProperties())); - - Long thingId = insert.executeWithKey(qt.id); - LOGGER.info("Inserted Thing. Created id = {}.", thingId); - t.setId(new LongId(thingId)); - - // Create new Locations, if any. - List locationIds = new ArrayList<>(); - for (Location l : t.getLocations()) { - entityExistsOrCreate(l); - Long lId = (Long) l.getId().getValue(); - - QThingsLocations qtl = QThingsLocations.thingsLocations; - insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", lId, thingId); - locationIds.add(lId); - } - - // Now link the new locations also to a historicalLocation. - if (!locationIds.isEmpty()) { - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - Long histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - for (Long locId : locationIds) { - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locId, histLocationId); - } - } - - // Create new datastreams, if any. - for (Datastream ds : t.getDatastreams()) { - ds.setThing(new ThingBuilder().setId(t.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : t.getMultiDatastreams()) { - mds.setThing(new ThingBuilder().setId(t.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - // TODO: if we allow the creation of historicalLocations through Things - // then we have to be able to link those to Locations we might have just created. - // However, id juggling will be needed! - return true; - } - - public boolean updateThing(Thing t, long thingId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QThings qt = QThings.things; - SQLUpdateClause update = qFactory.update(qt); - if (t.isSetName()) { - if (t.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qt.name, t.getName()); - } - if (t.isSetDescription()) { - if (t.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qt.description, t.getDescription()); - } - if (t.isSetProperties()) { - update.set(qt.properties, objectToJson(t.getProperties())); - } - update.where(qt.id.eq(thingId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Thing {} caused {} rows to change!", thingId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Thing {}", thingId); - - // Link existing Datastreams to the thing. - for (Datastream ds : t.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("Datastream with no id or non existing."); - } - Long dsId = (Long) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.thingId, thingId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to thing {}.", dsId, thingId); - } - } - - // Link existing MultiDatastreams to the thing. - for (MultiDatastream mds : t.getMultiDatastreams()) { - if (mds.getId() == null || !entityExists(mds)) { - throw new NoSuchEntityException("MultiDatastream with no id or non existing."); - } - Long mdsId = (Long) mds.getId().getValue(); - QMultiDatastreams qmds = QMultiDatastreams.multiDatastreams; - long mdsCount = qFactory.update(qmds) - .set(qmds.thingId, thingId) - .where(qmds.id.eq(mdsId)) - .execute(); - if (mdsCount > 0) { - LOGGER.info("Assigned multiDatastream {} to thing {}.", mdsId, thingId); - } - } - - // Link existing locations to the thing. - if (!t.getLocations().isEmpty()) { - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new locations to Thing, track the ids. - List locationIds = new ArrayList<>(); - for (Location l : t.getLocations()) { - if (l.getId() == null || !entityExists(l)) { - throw new NoSuchEntityException("Location with no id."); - } - Long locationId = (Long) l.getId().getValue(); - - SQLInsertClause insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - locationIds.add(locationId); - } - - // Now link the newly linked locations also to a historicalLocation. - if (!locationIds.isEmpty()) { - QHistLocations qhl = QHistLocations.histLocations; - SQLInsertClause insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - Long histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - for (Long locId : locationIds) { - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locId, histLocationId); - } - } - } - return true; - } - - private static T insertTimeValue(T clause, DateTimePath startPath, DateTimePath endPath, TimeValue time) { - if (time instanceof TimeInstant) { - TimeInstant timeInstant = (TimeInstant) time; - insertTimeInstant(clause, endPath, timeInstant); - return insertTimeInstant(clause, startPath, timeInstant); - } else if (time instanceof TimeInterval) { - TimeInterval timeInterval = (TimeInterval) time; - return insertTimeInterval(clause, startPath, endPath, timeInterval); - } - return clause; - } - - private static T insertTimeInstant(T clause, DateTimePath path, TimeInstant time) { - if (time == null) { - return clause; - } - clause.set(path, new Timestamp(time.getDateTime().getMillis())); - return clause; - } - - private static T insertTimeInterval(T clause, DateTimePath startPath, DateTimePath endPath, TimeInterval time) { - if (time == null) { - return clause; - } - Interval interval = time.getInterval(); - clause.set(startPath, new Timestamp(interval.getStartMillis())); - clause.set(endPath, new Timestamp(interval.getEndMillis())); - return clause; - } - - /** - * Sets both the geometry and location in the clause. - * - * @param The type of the clause. - * @param clause The insert or update clause to add to. - * @param locationPath The path to the location column. - * @param geomPath The path to the geometry column. - * @param encodingType The encoding type. - * @param location The location. - * @return The insert or update clause. - */ - private T insertGeometry(T clause, StringPath locationPath, GeometryPath geomPath, String encodingType, final Object location) { - if (encodingType != null && GeoJsonDeserializier.encodings.contains(encodingType.toLowerCase())) { - String locJson; - try { - locJson = new GeoJsonSerializer().serialize(location); - } catch (JsonProcessingException ex) { - LOGGER.error("Failed to store.", ex); - throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); - } - - // Postgres does not support Feature. - Object geoLocation = location; - if (location instanceof Feature) { - geoLocation = ((Feature) location).getGeometry(); - } - // Ensure the geoJson has a crs, otherwise Postgres complains. - if (geoLocation instanceof GeoJsonObject) { - GeoJsonObject geoJsonObject = (GeoJsonObject) geoLocation; - Crs crs = geoJsonObject.getCrs(); - if (crs == null) { - crs = new Crs(); - crs.setType(CrsType.name); - crs.getProperties().put("name", "EPSG:4326"); - geoJsonObject.setCrs(crs); - } - } - String geoJson; - try { - geoJson = new GeoJsonSerializer().serialize(geoLocation); - } catch (JsonProcessingException ex) { - LOGGER.error("Failed to store.", ex); - throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); - } - - try { - // geojson.jackson allows invalid polygons, geolatte catches those. - new JsonMapper().fromJson(geoJson, Geometry.class); - } catch (JsonException ex) { - throw new IllegalArgumentException("Invalid geoJson: " + ex.getMessage()); - } - clause.set(geomPath, Expressions.template(Geometry.class, "ST_Force2D(ST_Transform(ST_GeomFromGeoJSON({0}), 4326))", geoJson)); - clause.set(locationPath, locJson); - } else { - String json; - json = objectToJson(location); - clause.setNull(geomPath); - clause.set(locationPath, json); - } - return clause; - } - - private Object reParseGeometry(String encodingType, Object object) { - String json = objectToJson(object); - return PropertyHelper.locationFromEncoding(encodingType, json); - } - - /** - * Throws an exception if the entity has an id, but does not exist or if the - * entity can not be created. - * - * @param pm the persistenceManager - * @param e The Entity to check. - * @throws NoSuchEntityException If the entity has an id, but does not - * exist. - * @throws IncompleteEntityException If the entity has no id, but is not - * complete and can thus not be created. - */ - private void entityExistsOrCreate(Entity e) throws NoSuchEntityException, IncompleteEntityException { - if (e != null && e.getId() == null) { - e.complete(); - pm.insert(e); - } else if (e == null || !entityExists(e)) { - if (e == null) { - throw new NoSuchEntityException("No entity!"); - } - throw new NoSuchEntityException("No such entity '" + e.getEntityType() + "' with id " + e.getId().getValue()); - } - } - - public boolean entityExists(Entity e) { - if (e == null || e.getId() == null) { - return false; - } - long id = (long) e.getId().getValue(); - SQLQueryFactory qFactory = pm.createQueryFactory(); - long count = 0; - switch (e.getEntityType()) { - case Datastream: - QDatastreams d = QDatastreams.datastreams; - count = qFactory.select() - .from(d) - .where(d.id.eq(id)) - .fetchCount(); - break; - - case MultiDatastream: - QMultiDatastreams md = QMultiDatastreams.multiDatastreams; - count = qFactory.select() - .from(md) - .where(md.id.eq(id)) - .fetchCount(); - break; - - case FeatureOfInterest: - QFeatures foi = QFeatures.features; - count = qFactory.select() - .from(foi) - .where(foi.id.eq(id)) - .fetchCount(); - break; - - case HistoricalLocation: - QHistLocations h = QHistLocations.histLocations; - count = qFactory.select() - .from(h) - .where(h.id.eq(id)) - .fetchCount(); - break; - - case Location: - QLocations l = QLocations.locations; - count = qFactory.select() - .from(l) - .where(l.id.eq(id)) - .fetchCount(); - break; - - case Observation: - QObservations o = QObservations.observations; - count = qFactory.select() - .from(o) - .where(o.id.eq(id)) - .fetchCount(); - break; - - case ObservedProperty: - QObsProperties op = QObsProperties.obsProperties; - count = qFactory.select() - .from(op) - .where(op.id.eq(id)) - .fetchCount(); - break; - - case Sensor: - QSensors s = QSensors.sensors; - count = qFactory.select() - .from(s) - .where(s.id.eq(id)) - .fetchCount(); - break; - - case Thing: - QThings t = QThings.things; - count = qFactory.select() - .from(t) - .where(t.id.eq(id)) - .fetchCount(); - break; - - default: - throw new AssertionError(e.getEntityType().name()); - } - if (count > 1) { - LOGGER.error("More than one instance of {} with id {}.", e.getEntityType(), id); - } - return count > 0; - } - - public boolean entityExists(ResourcePath path) { - long count = pm.count(path, null); - if (count > 1) { - LOGGER.error("More than one instance of {}", path.toString()); - } - return count > 0; - } - - public String objectToJson(Object object) { - if (object == null) { - return null; - } - try { - return getFormatter().writeValueAsString(object); - } catch (IOException ex) { - throw new IllegalStateException("Could not serialise object.", ex); - } - } - - public ObjectMapper getFormatter() { - if (formatter == null) { - formatter = new ObjectMapper(); - } - return formatter; - } - -} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/IdGenerationHandlerLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/IdGenerationHandlerLong.java new file mode 100644 index 000000000..c4045f0b8 --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/IdGenerationHandlerLong.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * Copyright (C) 2018 KIT TECO, Vincenz-Prießnitz-Str. 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.sta.persistence.postgres.longid; + +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.IdGenerationHandler; + +/** + * + * Class for handling the persistence setting "idGenerationMode". + * + * @author koepke, scf + */ +public class IdGenerationHandlerLong extends IdGenerationHandler { + + /** + * Constructor for IdGenerationHandler. + * + * @param entity Entity for which idGenerationMode should be + * checked/applied. + */ + public IdGenerationHandlerLong(Entity entity) { + super(entity); + } + + /** + * + * Modify the entity id. + * + */ + @Override + public void modifyClientSuppliedId() { + // Nothing to do for now. + } + + /** + * + * Checks if a client generated id is valid. + * + * @return true if client generated id is valid. + */ + @Override + protected boolean validateClientSuppliedId() { + return getIdValue() != null; + } +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PathSqlBuilderLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PathSqlBuilderLong.java deleted file mode 100644 index 27fc50b6b..000000000 --- a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PathSqlBuilderLong.java +++ /dev/null @@ -1,664 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.longid; - -import com.querydsl.core.Tuple; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.NumberPath; -import com.querydsl.sql.RelationalPathBase; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyArrayIndex; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PathSqlBuilder; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; -import de.fraunhofer.iosb.ilt.sta.query.OrderBy; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.settings.PersistenceSettings; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - */ -public class PathSqlBuilderLong implements PathSqlBuilder { - - public static class TableRefLong implements TableRef { - - public EntityType type; - public RelationalPathBase qPath; - public NumberPath idPath; - - public TableRefLong() { - } - - public TableRefLong(TableRefLong source) { - type = source.type; - qPath = source.qPath; - idPath = source.idPath; - } - - @Override - public EntityType getType() { - return type; - } - - @Override - public RelationalPathBase getqPath() { - return qPath; - } - - @Override - public void clear() { - type = null; - qPath = null; - idPath = null; - } - - @Override - public TableRef copy() { - TableRefLong copy = new TableRefLong(this); - return copy; - } - - @Override - public boolean isEmpty() { - return type == null && qPath == null; - } - } - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PathSqlBuilderLong.class); - /** - * The prefix used for table aliases. The main entity is always - * <PREFIX>1. - */ - public static final String ALIAS_PREFIX = "e"; - private SQLQueryFactory queryFactory; - private SQLQuery sqlQuery; - private Set selectedProperties; - private final TableRefLong lastPath = new TableRefLong(); - private TableRefLong mainTable; - private int aliasNr = 0; - private boolean isFilter = false; - private boolean needsDistinct = false; - - @Override - public synchronized SQLQuery buildFor(ResourcePath path, Query query, SQLQueryFactory sqlQueryFactory, PersistenceSettings settings) { - this.queryFactory = sqlQueryFactory; - selectedProperties = new HashSet<>(); - sqlQuery = queryFactory.select(new Expression[]{}); - lastPath.clear(); - aliasNr = 0; - List elements = new ArrayList<>(path.getPathElements()); - - int count = elements.size(); - for (int i = count - 1; i >= 0; i--) { - ResourcePathElement element = elements.get(i); - element.visit(this); - } - - if (query != null) { - boolean distict = false; - PgExpressionHandler handler = new PgExpressionHandler(this, mainTable.copy()); - for (OrderBy ob : query.getOrderBy()) { - handler.addOrderbyToQuery(ob, sqlQuery); - } - if (needsDistinct) { - sqlQuery.distinct(); - distict = true; - } - isFilter = true; - needsDistinct = false; - de.fraunhofer.iosb.ilt.sta.query.expression.Expression filter = query.getFilter(); - if (filter != null) { - handler.addFilterToQuery(filter, sqlQuery); - } - if (settings.getAlwaysOrderbyId()) { - sqlQuery.orderBy(mainTable.idPath.asc()); - } - if (needsDistinct && !distict) { - sqlQuery.distinct(); - } - } - - return sqlQuery; - } - - @Override - public void visit(EntityPathElement element) { - queryEntityType(element.getEntityType(), element.getId(), lastPath); - } - - @Override - public void visit(EntitySetPathElement element) { - queryEntityType(element.getEntityType(), null, lastPath); - } - - @Override - public void visit(PropertyPathElement element) { - selectedProperties.add(element.getProperty()); - selectedProperties.add(EntityProperty.Id); - } - - @Override - public void visit(CustomPropertyPathElement element) { - // noting to do for custom properties. - } - - @Override - public void visit(CustomPropertyArrayIndex element) { - // noting to do for custom properties. - } - - @Override - public void queryEntityType(EntityType type, Id targetId, TableRef lastRef) { - if (!(lastRef instanceof TableRefLong)) { - throw new IllegalArgumentException("This implementation expect a TableRefLong"); - } - TableRefLong last = (TableRefLong) lastRef; - - Long id = null; - if (targetId != null) { - if (targetId.getBasicPersistenceType() != BasicPersistenceType.Integer) { - throw new IllegalArgumentException("This implementation expects Long ids, not " + targetId.getBasicPersistenceType()); - } - id = (Long) targetId.asBasicPersistenceType(); - } - switch (type) { - case Datastream: - queryDatastreams(id, last); - break; - - case MultiDatastream: - queryMultiDatastreams(id, last); - break; - - case FeatureOfInterest: - queryFeatures(id, last); - break; - - case HistoricalLocation: - queryHistLocations(id, last); - break; - - case Location: - queryLocations(id, last); - break; - - case Observation: - queryObservations(id, last); - break; - - case ObservedProperty: - queryObsProperties(id, last); - break; - - case Sensor: - querySensors(id, last); - break; - - case Thing: - queryThings(id, last); - break; - - default: - LOGGER.error("Unknown entity type {}!?", type); - throw new IllegalStateException("Unknown entity type " + type); - } - if (mainTable == null && !last.isEmpty()) { - mainTable = new TableRefLong(last); - } - - } - - @Override - public Map> expressionsForProperty(EntityProperty property, Path qPath, Map> target) { - return PropertyResolver.expressionsForProperty(property, qPath, target); - } - - private void queryDatastreams(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QDatastreams qDataStreams = new QDatastreams(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qDataStreams, selectedProperties)); - sqlQuery.from(qDataStreams); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.thingId.eq(qThings.id)); - needsDistinct = true; - break; - - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.id.eq(qObservations.datastreamId)); - break; - - case Sensor: - QSensors qSensors = (QSensors) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.sensorId.eq(qSensors.id)); - needsDistinct = true; - break; - - case ObservedProperty: - QObsProperties qObsProperties = (QObsProperties) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.obsPropertyId.eq(qObsProperties.id)); - needsDistinct = true; - break; - - case Datastream: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Datastreams.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Datastream; - last.qPath = qDataStreams; - last.idPath = qDataStreams.id; - } - if (entityId != null) { - sqlQuery.where(qDataStreams.id.eq(entityId)); - } - } - - private void queryMultiDatastreams(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QMultiDatastreams qMultiDataStreams = new QMultiDatastreams(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qMultiDataStreams, selectedProperties)); - sqlQuery.from(qMultiDataStreams); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.thingId.eq(qThings.id)); - needsDistinct = true; - break; - - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.id.eq(qObservations.multiDatastreamId)); - break; - - case Sensor: - QSensors qSensors = (QSensors) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.sensorId.eq(qSensors.id)); - needsDistinct = true; - break; - - case ObservedProperty: - QObsProperties qObsProperties = (QObsProperties) last.qPath; - QMultiDatastreamsObsProperties qMdOp = new QMultiDatastreamsObsProperties(alias + "j1"); - sqlQuery.innerJoin(qMdOp).on(qObsProperties.id.eq(qMdOp.obsPropertyId)); - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.id.eq(qMdOp.multiDatastreamId)); - if (!isFilter) { - sqlQuery.orderBy(qMdOp.rank.asc()); - } else { - needsDistinct = true; - } - break; - - case MultiDatastream: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Datastreams.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.MultiDatastream; - last.qPath = qMultiDataStreams; - last.idPath = qMultiDataStreams.id; - } - if (entityId != null) { - sqlQuery.where(qMultiDataStreams.id.eq(entityId)); - } - } - - private void queryThings(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QThings qThings = new QThings(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qThings, selectedProperties)); - sqlQuery.from(qThings); - } else { - switch (last.type) { - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qDatastreams.thingId)); - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qMultiDatastreams.thingId)); - break; - - case HistoricalLocation: - QHistLocations qHistLocations = (QHistLocations) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qHistLocations.thingId)); - break; - - case Location: - QLocations qLocations = (QLocations) last.qPath; - QThingsLocations qTL = new QThingsLocations(alias + "j1"); - sqlQuery.innerJoin(qTL).on(qLocations.id.eq(qTL.locationId)); - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qTL.thingId)); - needsDistinct = true; - break; - - case Thing: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Things.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Thing; - last.qPath = qThings; - last.idPath = qThings.id; - } - if (entityId != null) { - sqlQuery.where(qThings.id.eq(entityId)); - } - } - - private void queryFeatures(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QFeatures qFeatures = new QFeatures(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qFeatures, selectedProperties)); - sqlQuery.from(qFeatures); - } else { - switch (last.type) { - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qFeatures).on(qFeatures.id.eq(qObservations.featureId)); - break; - - case FeatureOfInterest: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Features.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.FeatureOfInterest; - last.qPath = qFeatures; - last.idPath = qFeatures.id; - } - if (entityId != null) { - sqlQuery.where(qFeatures.id.eq(entityId)); - } - } - - private void queryHistLocations(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QHistLocations qHistLocations = new QHistLocations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qHistLocations, selectedProperties)); - sqlQuery.from(qHistLocations); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qHistLocations).on(qThings.id.eq(qHistLocations.thingId)); - needsDistinct = true; - break; - - case Location: - QLocations qLocations = (QLocations) last.qPath; - QLocationsHistLocations qLHL = new QLocationsHistLocations(alias + "j1"); - sqlQuery.innerJoin(qLHL).on(qLocations.id.eq(qLHL.locationId)); - sqlQuery.innerJoin(qHistLocations).on(qHistLocations.id.eq(qLHL.histLocationId)); - needsDistinct = true; - break; - - case HistoricalLocation: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto HistLocations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.HistoricalLocation; - last.qPath = qHistLocations; - last.idPath = qHistLocations.id; - } - if (entityId != null) { - sqlQuery.where(qHistLocations.id.eq(entityId)); - } - } - - private void queryLocations(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QLocations qLocations = new QLocations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qLocations, selectedProperties)); - sqlQuery.from(qLocations); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - QThingsLocations qTL = new QThingsLocations(alias + "j1"); - sqlQuery.innerJoin(qTL).on(qThings.id.eq(qTL.thingId)); - sqlQuery.innerJoin(qLocations).on(qLocations.id.eq(qTL.locationId)); - needsDistinct = true; - break; - - case HistoricalLocation: - QHistLocations qHistLocations = (QHistLocations) last.qPath; - QLocationsHistLocations qLHL = new QLocationsHistLocations(alias + "j1"); - sqlQuery.innerJoin(qLHL).on(qHistLocations.id.eq(qLHL.histLocationId)); - sqlQuery.innerJoin(qLocations).on(qLocations.id.eq(qLHL.locationId)); - needsDistinct = true; - break; - - case Location: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Locations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Location; - last.qPath = qLocations; - last.idPath = qLocations.id; - } - if (entityId != null) { - sqlQuery.where(qLocations.id.eq(entityId)); - } - } - - private void querySensors(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QSensors qSensors = new QSensors(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qSensors, selectedProperties)); - sqlQuery.from(qSensors); - } else { - switch (last.type) { - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qSensors).on(qSensors.id.eq(qDatastreams.sensorId)); - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qSensors).on(qSensors.id.eq(qMultiDatastreams.sensorId)); - break; - - case Sensor: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Sensors.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Sensor; - last.qPath = qSensors; - last.idPath = qSensors.id; - } - if (entityId != null) { - sqlQuery.where(qSensors.id.eq(entityId)); - } - } - - private void queryObservations(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QObservations qObservations = new QObservations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qObservations, selectedProperties)); - sqlQuery.from(qObservations); - } else { - switch (last.type) { - case FeatureOfInterest: - QFeatures qFeatures = (QFeatures) last.qPath; - sqlQuery.innerJoin(qObservations).on(qFeatures.id.eq(qObservations.featureId)); - needsDistinct = true; - break; - - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qObservations).on(qDatastreams.id.eq(qObservations.datastreamId)); - needsDistinct = true; - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qObservations).on(qMultiDatastreams.id.eq(qObservations.multiDatastreamId)); - needsDistinct = true; - break; - - case Observation: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Observations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Observation; - last.qPath = qObservations; - last.idPath = qObservations.id; - } - if (entityId != null) { - sqlQuery.where(qObservations.id.eq(entityId)); - } - } - - private void queryObsProperties(Long entityId, TableRefLong last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QObsProperties qObsProperties = new QObsProperties(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qObsProperties, selectedProperties)); - sqlQuery.from(qObsProperties); - } else { - switch (last.type) { - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - QMultiDatastreamsObsProperties qMdOp = new QMultiDatastreamsObsProperties(alias + "j1"); - sqlQuery.innerJoin(qMdOp).on(qMultiDatastreams.id.eq(qMdOp.multiDatastreamId)); - sqlQuery.innerJoin(qObsProperties).on(qObsProperties.id.eq(qMdOp.obsPropertyId)); - needsDistinct = true; - needsDistinct = true; - break; - - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qObsProperties).on(qObsProperties.id.eq(qDatastreams.obsPropertyId)); - break; - case ObservedProperty: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto ObsProperties.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.ObservedProperty; - last.qPath = qObsProperties; - last.idPath = qObsProperties.id; - } - if (entityId != null) { - sqlQuery.where(qObsProperties.id.eq(entityId)); - } - } - -} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PostgresPersistenceManagerLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PostgresPersistenceManagerLong.java index 54d99381e..83588cb04 100644 --- a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PostgresPersistenceManagerLong.java +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PostgresPersistenceManagerLong.java @@ -17,502 +17,95 @@ */ package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid; -import com.querydsl.core.Tuple; -import com.querydsl.sql.SQLExpressions; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import com.querydsl.sql.SQLTemplates; -import com.querydsl.sql.dml.SQLDeleteClause; -import com.querydsl.sql.spatial.PostGISTemplates; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; +import com.querydsl.core.types.dsl.NumberPath; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.persistence.AbstractPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManagerlong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.IdGenerationHandler; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; -import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PropertyResolver; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QDatastreamsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QFeaturesLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QHistLocationsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QLocationsHistLocationsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QLocationsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QMultiDatastreamsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QMultiDatastreamsObsPropertiesLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QObsPropertiesLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QObservationsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QSensorsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QThingsLocationsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths.QThingsLong; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; -import de.fraunhofer.iosb.ilt.sta.settings.Settings; -import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; -import java.io.StringWriter; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.List; -import java.util.Map; -import javax.inject.Provider; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.sql.DataSource; -import liquibase.Contexts; -import liquibase.Liquibase; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.database.jvm.JdbcConnection; -import liquibase.exception.DatabaseException; -import liquibase.exception.LiquibaseException; -import liquibase.resource.ClassLoaderResourceAccessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * - * @author jab, scf + * @author jab + * @author scf */ -public class PostgresPersistenceManagerLong extends AbstractPersistenceManager implements PostgresPersistenceManager { +public class PostgresPersistenceManagerLong extends PostgresPersistenceManager, Long> { private static final String LIQUIBASE_CHANGELOG_FILENAME = "liquibase/tables.xml"; - private static final Logger LOGGER = LoggerFactory.getLogger(PostgresPersistenceManagerLong.class); - private static class MyConnectionWrapper implements Provider { - - private final CoreSettings settings; - private Connection connection; - - public MyConnectionWrapper(CoreSettings settings) { - this.settings = settings; - } - - @Override - public Connection get() { - if (connection == null) { - try { - connection = getConnection(settings); - } catch (NamingException | SQLException ex) { - LOGGER.error("Could not inizialize " + getClass().getName(), ex); - } - } - return connection; - } - - public void clear() { - connection = null; - } - - } - - private MyConnectionWrapper connectionProvider; - private SQLQueryFactory queryFactory; - private CoreSettings settings; - - public PostgresPersistenceManagerLong() { - - } + private static final IdManagerlong ID_MANAGER = new IdManagerlong(); + private static EntityFactories, Long> entityFactories; + private static PropertyResolver, Long> propertyResolver; @Override public IdManager getIdManager() { - return IdManager.ID_MANAGER_LONG; + return ID_MANAGER; } @Override public void init(CoreSettings settings) { - this.settings = settings; - connectionProvider = new MyConnectionWrapper(settings); - } - - @Override - public CoreSettings getCoreSettings() { - return settings; - } - - @Override - public boolean doInsert(Entity entity) throws NoSuchEntityException, IncompleteEntityException { - EntityInserter ei = new EntityInserter(this); - switch (entity.getEntityType()) { - case Datastream: - ei.insertDatastream((Datastream) entity); - break; - - case MultiDatastream: - ei.insertMultiDatastream((MultiDatastream) entity); - break; - - case FeatureOfInterest: - ei.insertFeatureOfInterest((FeatureOfInterest) entity); - break; - - case HistoricalLocation: - ei.insertHistoricalLocation((HistoricalLocation) entity); - break; - - case Location: - ei.insertLocation((Location) entity); - break; - - case Observation: - ei.insertObservation((Observation) entity); - break; - - case ObservedProperty: - ei.insertObservedProperty((ObservedProperty) entity); - break; - - case Sensor: - ei.insertSensor((Sensor) entity); - break; - - case Thing: - ei.insertThing((Thing) entity); - break; - - default: - throw new IllegalStateException("Unknown entity type: " + entity.getEntityType().name()); - + super.init(settings); + IdGenerationHandlerLong.setIdGenerationMode(settings.getPersistenceSettings().getIdGenerationMode()); + if (entityFactories == null) { + QCollection qCollection = new QCollection( + QDatastreamsLong.DATASTREAMS, + QFeaturesLong.FEATURES, + QHistLocationsLong.HISTLOCATIONS, + QLocationsLong.LOCATIONS, + QMultiDatastreamsLong.MULTIDATASTREAMS, + QObsPropertiesLong.OBSPROPERTIES, + QObservationsLong.OBSERVATIONS, + QSensorsLong.SENSORS, + QThingsLong.THINGS, + QLocationsHistLocationsLong.LOCATIONSHISTLOCATIONS, + QMultiDatastreamsObsPropertiesLong.MULTIDATASTREAMSOBSPROPERTIES, + QThingsLocationsLong.THINGSLOCATIONS); + init(qCollection); } - return true; } - @Override - public boolean doDelete(EntityPathElement pathElement) throws NoSuchEntityException { - SQLQueryFactory qf = createQueryFactory(); - long id = (long) pathElement.getId().getValue(); - SQLDeleteClause delete; - EntityType type = pathElement.getEntityType(); - switch (type) { - case Datastream: - delete = qf.delete(QDatastreams.datastreams).where(QDatastreams.datastreams.id.eq(id)); - break; - - case MultiDatastream: - delete = qf.delete(QMultiDatastreams.multiDatastreams).where(QMultiDatastreams.multiDatastreams.id.eq(id)); - break; - - case FeatureOfInterest: - delete = qf.delete(QFeatures.features).where(QFeatures.features.id.eq(id)); - break; - - case HistoricalLocation: - delete = qf.delete(QHistLocations.histLocations).where(QHistLocations.histLocations.id.eq(id)); - break; - - case Location: { - delete = qf.delete(QLocations.locations).where(QLocations.locations.id.eq(id)); - long count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} Locations", count); - - // Also delete all historicalLocations that no longer reference any location - QHistLocations qhl = QHistLocations.histLocations; - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - delete = qf.delete(qhl) - .where(qhl.id.in( - SQLExpressions.select(qhl.id) - .from(qhl) - .leftJoin(qlhl).on(qhl.id.eq(qlhl.histLocationId)) - .where(qlhl.locationId.isNull()) - )); - count = delete.execute(); - LOGGER.debug("Deleted {} HistoricalLocations", count); - return true; - } - - case Observation: - delete = qf.delete(QObservations.observations).where(QObservations.observations.id.eq(id)); - break; - - case ObservedProperty: { - // First delete all MultiDatastreams that link to this ObservedProperty. - QMultiDatastreams qMd = QMultiDatastreams.multiDatastreams; - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - delete = qf.delete(qMd).where(qMd.id.in( - SQLExpressions.select(qMdOp.multiDatastreamId).from(qMdOp).where(qMdOp.obsPropertyId.eq(id)) - )); - long count = delete.execute(); - LOGGER.debug("Deleted {} MultiDatastreams.", count); - - delete = qf.delete(QObsProperties.obsProperties).where(QObsProperties.obsProperties.id.eq(id)); - count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} ObservedProperties", count); - return true; - } - - case Sensor: - delete = qf.delete(QSensors.sensors).where(QSensors.sensors.id.eq(id)); - break; - - case Thing: - delete = qf.delete(QThings.things).where(QThings.things.id.eq(id)); - break; - - default: - throw new NoSuchEntityException("Unknown entity type: " + pathElement.getEntityType()); + private static synchronized void init(QCollection qCollection) { + if (entityFactories == null) { + entityFactories = new EntityFactories(ID_MANAGER, qCollection); + propertyResolver = new PropertyResolver<>(entityFactories, BasicPersistenceType.INTEGER); } - if (delete != null) { - long count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} entries of type {}", count, type); - } - return true; } @Override - public boolean doUpdate(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException { - EntityInserter ei = new EntityInserter(this); - entity.setId(pathElement.getId()); - long id = (long) pathElement.getId().getValue(); - if (!ei.entityExists(entity)) { - throw new NoSuchEntityException("No entity of type " + pathElement.getEntityType() + " with id " + id); - } - EntityType type = pathElement.getEntityType(); - switch (type) { - case Datastream: - ei.updateDatastream((Datastream) entity, id); - break; - - case MultiDatastream: - ei.updateMultiDatastream((MultiDatastream) entity, id); - break; - - case FeatureOfInterest: - ei.updateFeatureOfInterest((FeatureOfInterest) entity, id); - break; - - case HistoricalLocation: - ei.updateHistoricalLocation((HistoricalLocation) entity, id); - break; - - case Location: - ei.updateLocation((Location) entity, id); - break; - - case Observation: - ei.updateObservation((Observation) entity, id); - break; - - case ObservedProperty: - ei.updateObservedProperty((ObservedProperty) entity, id); - break; - - case Sensor: - ei.updateSensor((Sensor) entity, id); - break; - - case Thing: - ei.updateThing((Thing) entity, id); - break; - - default: - throw new AssertionError(type.name()); - - } - - return true; + public PropertyResolver, Long> getPropertyResolver() { + return propertyResolver; } @Override - protected boolean doCommit() { - try { - if (!connectionProvider.get().isClosed()) { - connectionProvider.get().commit(); - return true; - } - } catch (SQLException ex) { - LOGGER.error("Exception rolling back.", ex); - } - return false; + public String getLiquibaseChangelogFilename() { + return LIQUIBASE_CHANGELOG_FILENAME; } @Override - protected boolean doRollback() { - try { - if (!connectionProvider.get().isClosed()) { - LOGGER.info("Rolling back changes."); - connectionProvider.get().rollback(); - return true; - } - } catch (SQLException ex) { - LOGGER.error("Exception rolling back.", ex); - } - return false; + public EntityFactories, Long> getEntityFactories() { + return entityFactories; } @Override - protected boolean doClose() { - try { - connectionProvider.get().close(); - return true; - } catch (SQLException ex) { - LOGGER.error("Exception closing.", ex); - } finally { - connectionProvider.clear(); - } - return false; - } - - public static Connection getConnection(CoreSettings settings) throws NamingException, SQLException { - Settings customSettings = settings.getPersistenceSettings().getCustomSettings(); - if (customSettings.contains(TAG_DATA_SOURCE)) { - String dataSourceName = customSettings.getString(TAG_DATA_SOURCE); - if (dataSourceName != null && !dataSourceName.isEmpty()) { - InitialContext cxt = new InitialContext(); - if (cxt == null) { - throw new IllegalStateException("No context!"); - } - - DataSource ds = (DataSource) cxt.lookup("java:/comp/env/" + dataSourceName); - if (ds == null) { - throw new IllegalStateException("Data source not found!"); - } - Connection connection = ds.getConnection(); - connection.setAutoCommit(false); - return connection; - } - } - if (!customSettings.contains(TAG_DB_DRIVER) || customSettings.getString(TAG_DB_DRIVER).isEmpty()) { - throw new IllegalArgumentException("Property '" + TAG_DB_DRIVER + "' must be non-empty"); - } - try { - Class.forName(customSettings.getString(TAG_DB_DRIVER)); - } catch (ClassNotFoundException ex) { - LOGGER.error("Could not initialise database.", ex); - throw new IllegalArgumentException(ex); - } - - Connection connection = DriverManager.getConnection( - customSettings.getString(TAG_DB_URL), - customSettings.getString(TAG_DB_USERNAME), - customSettings.getString(TAG_DB_PASSWORD)); - - connection.setAutoCommit(false); - return connection; - } - - public SQLQueryFactory createQueryFactory() { - if (queryFactory == null) { - SQLTemplates templates = PostGISTemplates.builder().quote().build(); - queryFactory = new SQLQueryFactory(templates, connectionProvider); - } - return queryFactory; - } - - @Override - public boolean validatePath(ResourcePath path) { - ResourcePathElement element = path.getIdentifiedElement(); - if (element == null) { - return true; - } - ResourcePath tempPath = new ResourcePath(); - List elements = tempPath.getPathElements(); - while (element != null) { - elements.add(0, element); - element = element.getParent(); - } - return new EntityInserter(this).entityExists(tempPath); - } - - @Override - public Object get(ResourcePath path, Query query) { - ResourcePathElement lastElement = path.getLastElement(); - if (!(lastElement instanceof EntityPathElement) && !(lastElement instanceof EntitySetPathElement)) { - if (!query.getExpand().isEmpty()) { - LOGGER.warn("Expand only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); - query.getExpand().clear(); - } - if (!query.getSelect().isEmpty()) { - LOGGER.warn("Select only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); - query.getSelect().clear(); - } - } - - SQLQueryFactory qf = createQueryFactory(); - PathSqlBuilderLong psb = new PathSqlBuilderLong(); - SQLQuery sqlQuery = psb.buildFor(path, query, qf, settings.getPersistenceSettings()); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Generated SQL:\n{}", sqlQuery.getSQL().getSQL()); - } - - EntityCreator entityCreator = new EntityCreator(this, path, query, sqlQuery); - lastElement.visit(entityCreator); - Object entity = entityCreator.getEntity(); - - if (path.isValue() && entity instanceof Map) { - Map map = (Map) entity; - entity = map.get(entityCreator.getEntityName()); - } - - return entity; - } - - public long count(ResourcePath path, Query query) { - SQLQueryFactory qf = createQueryFactory(); - PathSqlBuilderLong psb = new PathSqlBuilderLong(); - SQLQuery sqlQuery = psb.buildFor(path, query, qf, settings.getPersistenceSettings()); - return sqlQuery.fetchCount(); - } - - @Override - public String checkForUpgrades() { - StringWriter out = new StringWriter(); - try { - Connection connection = getConnection(settings); - - Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); - Liquibase liquibase = new liquibase.Liquibase(LIQUIBASE_CHANGELOG_FILENAME, new ClassLoaderResourceAccessor(), database); - liquibase.update(new Contexts(), out); - database.commit(); - database.close(); - connection.close(); - - } catch (SQLException | DatabaseException | NamingException ex) { - LOGGER.error("Could not initialise database.", ex); - out.append("Failed to initialise database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } catch (LiquibaseException ex) { - LOGGER.error("Could not upgrade database.", ex); - out.append("Failed to upgrade database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } - return out.toString(); - } - - @Override - public String doUpgrades() { - StringWriter out = new StringWriter(); - try { - Connection connection = getConnection(settings); - - Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); - Liquibase liquibase = new liquibase.Liquibase(LIQUIBASE_CHANGELOG_FILENAME, new ClassLoaderResourceAccessor(), database); - liquibase.update(new Contexts()); - database.commit(); - database.close(); - connection.close(); - - } catch (SQLException | DatabaseException | NamingException ex) { - LOGGER.error("Could not initialise database.", ex); - out.append("Failed to initialise database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } catch (LiquibaseException ex) { - LOGGER.error("Could not upgrade database.", ex); - out.append("Failed to upgrade database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } - return out.toString(); + public IdGenerationHandler createIdGenerationHanlder(Entity e) { + return new IdGenerationHandlerLong(e); } } diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PropertyHelper.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PropertyHelper.java deleted file mode 100644 index 58d32a863..000000000 --- a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PropertyHelper.java +++ /dev/null @@ -1,831 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.longid; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.mysema.commons.lang.CloseableIterator; -import com.querydsl.core.Tuple; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.NumberPath; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.geojson.GeoJsonDeserializier; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.LongId; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.Property; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.ResultType; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.util.GeoHelper; -import java.io.IOException; -import java.math.BigDecimal; -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.geojson.Polygon; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author scf - */ -public class PropertyHelper { - - public static interface entityFromTupleFactory { - - /** - * Creates a T, reading the Tuple with a qObject using no alias. - * - * @param tuple The tuple to create the Entity from. - * @param query The query used to request the data. - * @param dataSize The counter for the data size. This counts only the - * variable-sided elements, such as Observation.result and - * Thing.properties. - * @return The Entity created from the Tuple. - */ - public T create(Tuple tuple, Query query, DataSize dataSize); - - /** - * Get the primary key of the table of the entity this factory - * - * @return The primary key of the table of the entity this factory - * creates, using no alias. - */ - public NumberPath getPrimaryKey(); - - /** - * Get the EntityType of the Entities created by this factory. - * - * @return The EntityType of the Entities created by this factory. - */ - public EntityType getEntityType(); - - } - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PropertyHelper.class); - private static final TypeReference> TYPE_LIST_STRING = new TypeReference>() { - // Empty on purpose. - }; - private static final TypeReference> TYPE_LIST_UOM = new TypeReference>() { - // Empty on purpose. - }; - private static final Map, entityFromTupleFactory> FACTORY_PER_ENTITY = new HashMap<>(); - - public static Expression[] getExpressions(Path qPath, Set selectedProperties) { - List> exprList = new ArrayList<>(); - if (selectedProperties.isEmpty()) { - PropertyResolver.expressionsForClass(qPath, exprList); - } else { - for (EntityProperty property : selectedProperties) { - PropertyResolver.expressionsForProperty(property, qPath, exprList); - } - } - return exprList.toArray(new Expression[exprList.size()]); - } - - public static EntitySet createSetFromTuples(entityFromTupleFactory factory, CloseableIterator tuples, Query query, long maxDataSize) { - EntitySet entitySet = new EntitySetImpl<>(factory.getEntityType()); - int count = 0; - DataSize size = new DataSize(); - int top = query.getTopOrDefault(); - while (tuples.hasNext()) { - Tuple tuple = tuples.next(); - entitySet.add(factory.create(tuple, query, size)); - count++; - if (count >= top) { - return entitySet; - } - if (size.getDataSize() > maxDataSize) { - LOGGER.debug("Size limit reached: {} > {}.", size.getDataSize(), maxDataSize); - return entitySet; - } - } - return entitySet; - } - - public static class DatastreamFactory implements PropertyHelper.entityFromTupleFactory { - - public static final DatastreamFactory withDefaultAlias = new DatastreamFactory(new QDatastreams(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QDatastreams qInstance; - - public DatastreamFactory(QDatastreams qInstance) { - this.qInstance = qInstance; - } - - @Override - public Datastream create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Datastream entity = new Datastream(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - entity.setObservationType(tuple.get(qInstance.observationType)); - - String observedArea = tuple.get(qInstance.observedArea.asText()); - if (observedArea != null) { - try { - Polygon polygon = GeoHelper.parsePolygon(observedArea); - entity.setObservedArea(polygon); - } catch (IllegalArgumentException e) { - // It's not a polygon, probably a point or a line. - } - } - ObservedProperty op = observedProperyFromId(tuple.get(qInstance.obsPropertyId)); - entity.setObservedProperty(op); - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - if (pTimeStart != null && pTimeEnd != null) { - entity.setPhenomenonTime(intervalFromTimes(pTimeStart, pTimeEnd)); - } - - Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); - Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); - if (rTimeStart != null && rTimeEnd != null) { - entity.setResultTime(intervalFromTimes(rTimeStart, rTimeEnd)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - entity.setSensor(sensorFromId(tuple.get(qInstance.sensorId))); - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - - entity.setUnitOfMeasurement(new UnitOfMeasurement(tuple.get(qInstance.unitName), tuple.get(qInstance.unitSymbol), tuple.get(qInstance.unitDefinition))); - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Datastream; - } - - } - - public static class MultiDatastreamFactory implements PropertyHelper.entityFromTupleFactory { - - public static final MultiDatastreamFactory withDefaultAlias = new MultiDatastreamFactory(new QMultiDatastreams(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QMultiDatastreams qInstance; - - public MultiDatastreamFactory(QMultiDatastreams qInstance) { - this.qInstance = qInstance; - } - - @Override - public MultiDatastream create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - MultiDatastream entity = new MultiDatastream(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - List observationTypes = jsonToObject(tuple.get(qInstance.observationTypes), TYPE_LIST_STRING); - entity.setMultiObservationDataTypes(observationTypes); - - String observedArea = tuple.get(qInstance.observedArea.asText()); - if (observedArea != null) { - try { - Polygon polygon = GeoHelper.parsePolygon(observedArea); - entity.setObservedArea(polygon); - } catch (IllegalArgumentException e) { - // It's not a polygon, probably a point or a line. - } - } - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - if (pTimeStart != null && pTimeEnd != null) { - entity.setPhenomenonTime(intervalFromTimes(pTimeStart, pTimeEnd)); - } - - Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); - Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); - if (rTimeStart != null && rTimeEnd != null) { - entity.setResultTime(intervalFromTimes(rTimeStart, rTimeEnd)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - entity.setSensor(sensorFromId(tuple.get(qInstance.sensorId))); - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - - List units = jsonToObject(tuple.get(qInstance.unitOfMeasurements), TYPE_LIST_UOM); - entity.setUnitOfMeasurements(units); - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.MultiDatastream; - } - - } - - public static class ThingFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ThingFactory withDefaultAlias = new ThingFactory(new QThings(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QThings qInstance; - - public ThingFactory(QThings qInstance) { - this.qInstance = qInstance; - } - - @Override - public Thing create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Thing entity = new Thing(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - dataSize.increase(props == null ? 0 : props.length()); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Thing; - } - - } - - public static class FeatureOfInterestFactory implements PropertyHelper.entityFromTupleFactory { - - public static final FeatureOfInterestFactory withDefaultAlias = new FeatureOfInterestFactory(new QFeatures(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QFeatures qInstance; - - public FeatureOfInterestFactory(QFeatures qInstance) { - this.qInstance = qInstance; - } - - @Override - public FeatureOfInterest create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - FeatureOfInterest entity = new FeatureOfInterest(); - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String encodingType = tuple.get(qInstance.encodingType); - entity.setEncodingType(encodingType); - - if (select.isEmpty() || select.contains(EntityProperty.Feature)) { - String locationString = tuple.get(qInstance.feature); - dataSize.increase(locationString == null ? 0 : locationString.length()); - entity.setFeature(locationFromEncoding(encodingType, locationString)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.FeatureOfInterest; - } - - } - - public static class HistoricalLocationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final HistoricalLocationFactory withDefaultAlias = new HistoricalLocationFactory(new QHistLocations(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QHistLocations qInstance; - - public HistoricalLocationFactory(QHistLocations qInstance) { - this.qInstance = qInstance; - } - - @Override - public HistoricalLocation create(Tuple tuple, Query query, DataSize dataSize) { - HistoricalLocation entity = new HistoricalLocation(); - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - entity.setTime(instantFromTime(tuple.get(qInstance.time))); - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.HistoricalLocation; - } - - } - - public static class LocationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final LocationFactory withDefaultAlias = new LocationFactory(new QLocations(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QLocations qInstance; - - public LocationFactory(QLocations qInstance) { - this.qInstance = qInstance; - } - - @Override - public Location create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - Location entity = new Location(); - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String encodingType = tuple.get(qInstance.encodingType); - entity.setEncodingType(encodingType); - - if (select.isEmpty() || select.contains(EntityProperty.Location)) { - String locationString = tuple.get(qInstance.location); - dataSize.increase(locationString == null ? 0 : locationString.length()); - entity.setLocation(locationFromEncoding(encodingType, locationString)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Location; - } - - } - - public static class SensorFactory implements PropertyHelper.entityFromTupleFactory { - - public static final SensorFactory withDefaultAlias = new SensorFactory(new QSensors(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QSensors qInstance; - - public SensorFactory(QSensors qInstance) { - this.qInstance = qInstance; - } - - @Override - public Sensor create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Sensor entity = new Sensor(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - entity.setEncodingType(tuple.get(qInstance.encodingType)); - - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Metadata)) { - String metaDataString = tuple.get(qInstance.metadata); - dataSize.increase(metaDataString == null ? 0 : metaDataString.length()); - entity.setMetadata(metaDataString); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Sensor; - } - - } - - public static class ObservationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ObservationFactory withDefaultAlias = new ObservationFactory(new QObservations(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QObservations qInstance; - - public ObservationFactory(QObservations qInstance) { - this.qInstance = qInstance; - } - - @Override - public Observation create(Tuple tuple, Query query, DataSize dataSize) { - Observation entity = new Observation(); - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Long dsId = tuple.get(qInstance.datastreamId); - if (dsId != null) { - entity.setDatastream(datastreamFromId(dsId)); - } - Long mDsId = tuple.get(qInstance.multiDatastreamId); - if (mDsId != null) { - entity.setMultiDatastream(multiDatastreamFromId(mDsId)); - } - - entity.setFeatureOfInterest(featureOfInterestFromId(tuple.get(qInstance.featureId))); - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Parameters)) { - String props = tuple.get(qInstance.parameters); - dataSize.increase(props == null ? 0 : props.length()); - entity.setParameters(jsonToObject(props, Map.class)); - } - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - entity.setPhenomenonTime(valueFromTimes(pTimeStart, pTimeEnd)); - - if (select.isEmpty() || select.contains(EntityProperty.Result)) { - Byte resultTypeOrd = tuple.get(qInstance.resultType); - if (resultTypeOrd != null) { - ResultType resultType = ResultType.fromSqlValue(resultTypeOrd); - switch (resultType) { - case BOOLEAN: - entity.setResult(tuple.get(qInstance.resultBoolean)); - break; - - case NUMBER: - try { - entity.setResult(new BigDecimal(tuple.get(qInstance.resultString))); - } catch (NumberFormatException e) { - // It was not a Number? Use the double value. - entity.setResult(tuple.get(qInstance.resultNumber)); - } - break; - - case OBJECT_ARRAY: - String jsonData = tuple.get(qInstance.resultJson); - dataSize.increase(jsonData == null ? 0 : jsonData.length()); - entity.setResult(jsonToTree(jsonData)); - break; - - case STRING: - String stringData = tuple.get(qInstance.resultString); - dataSize.increase(stringData == null ? 0 : stringData.length()); - entity.setResult(stringData); - break; - } - } - } - - if (select.isEmpty() || select.contains(EntityProperty.ResultQuality)) { - String resultQuality = tuple.get(qInstance.resultQuality); - dataSize.increase(resultQuality == null ? 0 : resultQuality.length()); - entity.setResultQuality(jsonToObject(resultQuality, Object.class)); - } - - entity.setResultTime(instantFromTime(tuple.get(qInstance.resultTime))); - - Timestamp vTimeStart = tuple.get(qInstance.validTimeStart); - Timestamp vTimeEnd = tuple.get(qInstance.validTimeEnd); - if (vTimeStart != null && vTimeEnd != null) { - entity.setValidTime(intervalFromTimes(vTimeStart, vTimeEnd)); - } - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Observation; - } - - } - - public static class ObservedPropertyFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ObservedPropertyFactory withDefaultAlias = new ObservedPropertyFactory(new QObsProperties(PathSqlBuilderLong.ALIAS_PREFIX + "1")); - private final QObsProperties qInstance; - - public ObservedPropertyFactory(QObsProperties qInstance) { - this.qInstance = qInstance; - } - - @Override - public ObservedProperty create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - ObservedProperty entity = new ObservedProperty(); - entity.setDefinition(tuple.get(qInstance.definition)); - entity.setDescription(tuple.get(qInstance.description)); - Long id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new LongId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public NumberPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.ObservedProperty; - } - - } - - static { - FACTORY_PER_ENTITY.put(Datastream.class, DatastreamFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(MultiDatastream.class, MultiDatastreamFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Thing.class, ThingFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(FeatureOfInterest.class, FeatureOfInterestFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(HistoricalLocation.class, HistoricalLocationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Location.class, LocationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Sensor.class, SensorFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Observation.class, ObservationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(ObservedProperty.class, ObservedPropertyFactory.withDefaultAlias); - } - - /** - * Get the factory for the given entity class, using the default alias - * PathSqlBuilderLong.ALIAS_PREFIX + "1". - * - * @param The type of entity to get the factory for. - * @param clazz The class of the entity to get the factory for. - * @return the factory for the given entity class. - */ - public static entityFromTupleFactory getFactoryFor(Class clazz) { - entityFromTupleFactory factory = FACTORY_PER_ENTITY.get(clazz); - if (factory == null) { - throw new AssertionError("No factory found for " + clazz.getName()); - } - return (entityFromTupleFactory) factory; - } - - private static TimeInterval intervalFromTimes(Timestamp timeStart, Timestamp timeEnd) { - if (timeStart == null) { - timeStart = Timestamp.valueOf(LocalDateTime.MAX); - } - if (timeEnd == null) { - timeEnd = Timestamp.valueOf(LocalDateTime.MIN); - } - if (timeEnd.before(timeStart)) { - return null; - } else { - return TimeInterval.create(timeStart.getTime(), timeEnd.getTime()); - } - } - - private static TimeInstant instantFromTime(Timestamp time) { - if (time == null) { - return new TimeInstant(null); - } - return TimeInstant.create(time.getTime()); - } - - private static TimeValue valueFromTimes(Timestamp timeStart, Timestamp timeEnd) { - if (timeEnd == null || timeEnd.equals(timeStart)) { - return instantFromTime(timeStart); - } - return intervalFromTimes(timeStart, timeEnd); - } - - private static Datastream datastreamFromId(Long id) { - if (id == null) { - return null; - } - Datastream ds = new Datastream(); - ds.setId(new LongId(id)); - ds.setExportObject(false); - return ds; - } - - private static MultiDatastream multiDatastreamFromId(Long id) { - if (id == null) { - return null; - } - MultiDatastream ds = new MultiDatastream(); - ds.setId(new LongId(id)); - ds.setExportObject(false); - return ds; - } - - private static FeatureOfInterest featureOfInterestFromId(Long id) { - if (id == null) { - return null; - } - FeatureOfInterest foi = new FeatureOfInterest(); - foi.setId(new LongId(id)); - foi.setExportObject(false); - return foi; - } - - private static ObservedProperty observedProperyFromId(Long id) { - if (id == null) { - return null; - } - ObservedProperty op = new ObservedProperty(); - op.setId(new LongId(id)); - op.setExportObject(false); - return op; - } - - private static Sensor sensorFromId(Long id) { - if (id == null) { - return null; - } - Sensor sensor = new Sensor(); - sensor.setId(new LongId(id)); - sensor.setExportObject(false); - return sensor; - } - - private static Thing thingFromId(Long id) { - if (id == null) { - return null; - } - Thing thing = new Thing(); - thing.setId(new LongId(id)); - thing.setExportObject(false); - return thing; - } - - public static Object locationFromEncoding(String encodingType, String locationString) { - if (locationString == null || locationString.isEmpty()) { - return null; - } - if (encodingType != null && GeoJsonDeserializier.encodings.contains(encodingType.toLowerCase())) { - try { - Object geoJson = new GeoJsonDeserializier().deserialize(locationString); - return geoJson; - } catch (IOException ex) { - LOGGER.error("Failed to deserialise geoJson."); - - } - } else { - try { - Map map = jsonToObject(locationString, Map.class - ); - return map; - } catch (Exception e) { - LOGGER.trace("Not a map."); - } - return locationString; - } - return null; - } - - public static JsonNode jsonToTree(String json) { - if (json == null) { - return null; - } - - try { - return new ObjectMapper().readTree(json); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - - public static T jsonToObject(String json, Class clazz) { - if (json == null) { - return null; - } - try { - return new ObjectMapper().readValue(json, clazz); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - - public static T jsonToObject(String json, TypeReference typeReference) { - if (json == null) { - return null; - } - try { - return new ObjectMapper().readValue(json, typeReference); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - -} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PropertyResolver.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PropertyResolver.java deleted file mode 100644 index 06dbe1cc7..000000000 --- a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/PropertyResolver.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.longid; - -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; -import de.fraunhofer.iosb.ilt.sta.path.Property; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_END; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_START; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - */ -public class PropertyResolver { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PropertyResolver.class); - - private static interface ExpressionFactory { - - Expression get(T qPath); - } - - private static final Map> EP_MAP_SINGLE = new HashMap<>(); - private static final Map>> EP_MAP_MULTI = new HashMap<>(); - private static final Map> ALL_FOR_CLASS = new HashMap<>(); - - static { - addEntry(EntityProperty.Id, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.Name, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.name); - addEntry(EntityProperty.Description, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.description); - addEntry(EntityProperty.ObservationType, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.observationType); - addEntry(EntityProperty.ObservedArea, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.observedArea.asText()); - addEntry(EntityProperty.PhenomenonTime, QDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QDatastreams qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QDatastreams qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Properties, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.properties); - addEntry(EntityProperty.ResultTime, QDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QDatastreams qPath) -> qPath.resultTimeStart); - addEntry(EntityProperty.ResultTime, QDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QDatastreams qPath) -> qPath.resultTimeEnd); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "definition", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitDefinition); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "name", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitName); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "symbol", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitSymbol); - addEntry(NavigationProperty.Sensor, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.sensorId); - addEntry(NavigationProperty.ObservedProperty, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.obsPropertyId); - addEntry(NavigationProperty.Thing, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.Name, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.name); - addEntry(EntityProperty.Description, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.description); - addEntry(EntityProperty.MultiObservationDataTypes, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.observationTypes); - addEntry(EntityProperty.ObservedArea, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.observedArea.asText()); - addEntry(EntityProperty.PhenomenonTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Properties, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.properties); - addEntry(EntityProperty.ResultTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.resultTimeStart); - addEntry(EntityProperty.ResultTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.resultTimeEnd); - addEntry(EntityProperty.UnitOfMeasurements, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.unitOfMeasurements); - addEntry(NavigationProperty.Sensor, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.sensorId); - addEntry(NavigationProperty.Thing, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.id); - addEntry(EntityProperty.Name, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.name); - addEntry(EntityProperty.Description, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.encodingType); - addEntry(EntityProperty.Feature, QFeatures.class, "j", (ExpressionFactory) (QFeatures qPath) -> qPath.feature); - addEntry(EntityProperty.Feature, QFeatures.class, "g", (ExpressionFactory) (QFeatures qPath) -> qPath.geom); - addEntry(EntityProperty.Properties, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.id); - addEntry(EntityProperty.Time, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.time); - addEntry(NavigationProperty.Thing, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.id); - addEntry(EntityProperty.Name, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.name); - addEntry(EntityProperty.Description, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.encodingType); - addEntry(EntityProperty.Location, QLocations.class, "j", (ExpressionFactory) (QLocations qPath) -> qPath.location); - addEntry(EntityProperty.Location, QLocations.class, "g", (ExpressionFactory) (QLocations qPath) -> qPath.geom); - addEntry(EntityProperty.Properties, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.id); - addEntry(EntityProperty.Definition, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.definition); - addEntry(EntityProperty.Description, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.description); - addEntry(EntityProperty.Name, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.name); - addEntry(EntityProperty.Properties, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.id); - addEntry(EntityProperty.Parameters, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.parameters); - addEntry(EntityProperty.PhenomenonTime, QObservations.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QObservations qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QObservations.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QObservations qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Result, QObservations.class, "n", (ExpressionFactory) (QObservations qPath) -> qPath.resultNumber); - addEntry(EntityProperty.Result, QObservations.class, "b", (ExpressionFactory) (QObservations qPath) -> qPath.resultBoolean); - addEntry(EntityProperty.Result, QObservations.class, "s", (ExpressionFactory) (QObservations qPath) -> qPath.resultString); - addEntry(EntityProperty.Result, QObservations.class, "j", (ExpressionFactory) (QObservations qPath) -> qPath.resultJson); - addEntry(EntityProperty.Result, QObservations.class, "t", (ExpressionFactory) (QObservations qPath) -> qPath.resultType); - addEntry(EntityProperty.ResultQuality, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.resultQuality); - addEntry(EntityProperty.ResultTime, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.resultTime); - addEntry(EntityProperty.ValidTime, QObservations.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QObservations qPath) -> qPath.validTimeStart); - addEntry(EntityProperty.ValidTime, QObservations.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QObservations qPath) -> qPath.validTimeEnd); - addEntry(NavigationProperty.FeatureOfInterest, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.featureId); - addEntry(NavigationProperty.Datastream, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.datastreamId); - addEntry(NavigationProperty.MultiDatastream, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.multiDatastreamId); - - addEntry(EntityProperty.Id, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.id); - addEntry(EntityProperty.Name, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.name); - addEntry(EntityProperty.Description, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.encodingType); - addEntry(EntityProperty.Metadata, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.metadata); - addEntry(EntityProperty.Properties, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.id); - addEntry(EntityProperty.Name, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.name); - addEntry(EntityProperty.Description, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.description); - addEntry(EntityProperty.Properties, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.properties); - } - - /** - * - * @param qPath The path to get expressions for. - * @param target The list to add to. If null a new list will be created. - * @return The target list, or a new list if target was null. - */ - public static List> expressionsForClass(Path qPath, List> target) { - List list = ALL_FOR_CLASS.get(qPath.getClass()); - if (target == null) { - target = new ArrayList<>(); - } - for (ExpressionFactory f : list) { - target.add(f.get(qPath)); - } - return target; - } - - public static Expression expressionForProperty(EntityProperty property, Path qPath) { - Map innerMap = EP_MAP_SINGLE.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("ObservedProperty has no property called " + property.toString()); - } - return innerMap.get(qPath.getClass()).get(qPath); - } - - /** - * Get a list of expressions for the given property and path. Add it to the - * given list, or a new list. - * - * @param property The property to get expressions for. - * @param qPath The path to get expressions for. - * @param target The list to add to. If null a new list will be created. - * @return The target list, or a new list if target was null. - */ - public static List> expressionsForProperty(EntityProperty property, Path qPath, List< Expression> target) { - Map> innerMap = EP_MAP_MULTI.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("ObservedProperty has no property called " + property.toString()); - } - Map coreMap = innerMap.get(qPath.getClass()); - if (target == null) { - target = new ArrayList<>(); - } - for (Map.Entry es : coreMap.entrySet()) { - target.add(es.getValue().get(qPath)); - } - return target; - } - - /** - * Get a Map of expressions for the given property and path. Add it to the - * given Map, or a new Map. - * - * @param property The property to get expressions for. - * @param qPath The path to get expressions for. - * @param target The Map to add to. If null a new Map will be created. - * @return The target Map, or a new Map if target was null. - */ - public static Map> expressionsForProperty(EntityProperty property, Path qPath, Map> target) { - Map> innerMap = EP_MAP_MULTI.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("We do not know any property called " + property.toString()); - } - Map coreMap = innerMap.get(qPath.getClass()); - if (coreMap == null) { - throw new IllegalArgumentException("No property called " + property.toString() + " for " + qPath.getClass()); - } - if (target == null) { - target = new LinkedHashMap<>(); - } - for (Map.Entry es : coreMap.entrySet()) { - target.put(es.getKey(), es.getValue().get(qPath)); - } - return target; - } - - private static void addEntry(Property p, Class c, ExpressionFactory f) { - addEntrySingle(p, c, f); - addEntryMulti(p, c, null, f); - addToAll(c, f); - } - - private static void addEntry(Property p, Class c, String name, ExpressionFactory f) { - addEntrySingle(p, c, f); - addEntryMulti(p, c, name, f); - addToAll(c, f); - } - - private static void addToAll(Class c, ExpressionFactory f) { - List list = ALL_FOR_CLASS.get(c); - if (list == null) { - list = new ArrayList<>(); - ALL_FOR_CLASS.put(c, list); - } - list.add(f); - } - - private static void addEntrySingle(Property p, Class c, ExpressionFactory f) { - Map innerMap = EP_MAP_SINGLE.get(p); - if (innerMap == null) { - innerMap = new HashMap<>(); - EP_MAP_SINGLE.put(p, innerMap); - } - if (innerMap.containsKey(c)) { - LOGGER.trace("Class {} already has a registration for {}.", c.getName(), p); - return; - } - innerMap.put(c, f); - } - - private static void addEntryMulti(Property p, Class c, String name, ExpressionFactory f) { - Map> innerMap = EP_MAP_MULTI.get(p); - if (innerMap == null) { - innerMap = new HashMap<>(); - EP_MAP_MULTI.put(p, innerMap); - } - Map coreMap = innerMap.get(c); - if (coreMap == null) { - coreMap = new LinkedHashMap<>(); - innerMap.put(c, coreMap); - } - if (name == null) { - name = Integer.toString(coreMap.size()); - } - coreMap.put(name, f); - } -} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QDatastreamsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QDatastreamsLong.java new file mode 100644 index 000000000..d9d5be346 --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QDatastreamsLong.java @@ -0,0 +1,76 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import java.sql.Types; + +/** + * QDatastreamsLong is a Querydsl query type for QDatastreamsLong + */ +public class QDatastreamsLong extends AbstractQDatastreams, Long> { + + private static final long serialVersionUID = -222215350; + private static final String TABLE_NAME = "DATASTREAMS"; + + public static final QDatastreamsLong DATASTREAMS = new QDatastreamsLong(TABLE_NAME); + + private final NumberPath id = createNumber("id", Long.class); + + private final NumberPath obsPropertyId = createNumber("obsPropertyId", Long.class); + + private final NumberPath sensorId = createNumber("sensorId", Long.class); + + private final NumberPath thingId = createNumber("thingId", Long.class); + + public QDatastreamsLong(String variable) { + super(QDatastreamsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(obsPropertyId, ColumnMetadata.named("OBS_PROPERTY_ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(sensorId, ColumnMetadata.named("SENSOR_ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + /** + * @return the obsPropertyId + */ + @Override + public NumberPath getObsPropertyId() { + return obsPropertyId; + } + + /** + * @return the sensorId + */ + @Override + public NumberPath getSensorId() { + return sensorId; + } + + /** + * @return the thingId + */ + @Override + public NumberPath getThingId() { + return thingId; + } + + @Override + public QDatastreamsLong newWithAlias(String variable) { + return new QDatastreamsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QFeaturesLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QFeaturesLong.java new file mode 100644 index 000000000..39082bdbd --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QFeaturesLong.java @@ -0,0 +1,43 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQFeatures; +import java.sql.Types; + +/** + * QFeaturesLong is a Querydsl query type for QFeaturesLong + */ +public class QFeaturesLong extends AbstractQFeatures, Long> { + + private static final long serialVersionUID = 906833564; + private static final String TABLE_NAME = "FEATURES"; + + public static final QFeaturesLong FEATURES = new QFeaturesLong(TABLE_NAME); + + public final NumberPath id = createNumber("id", Long.class); + + public QFeaturesLong(String variable) { + super(QFeaturesLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public QFeaturesLong newWithAlias(String variable) { + return new QFeaturesLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QHistLocationsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QHistLocationsLong.java new file mode 100644 index 000000000..f16f52780 --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QHistLocationsLong.java @@ -0,0 +1,51 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import java.sql.Types; + +/** + * QHistLocationsLong is a Querydsl query type for QHistLocationsLong + */ +public class QHistLocationsLong extends AbstractQHistLocations, Long> { + + private static final long serialVersionUID = 244045661; + private static final String TABLE_NAME = "HIST_LOCATIONS"; + + public static final QHistLocationsLong HISTLOCATIONS = new QHistLocationsLong(TABLE_NAME); + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath thingId = createNumber("thingId", Long.class); + + public QHistLocationsLong(String variable) { + super(QHistLocationsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public NumberPath getThingId() { + return thingId; + } + + @Override + public QHistLocationsLong newWithAlias(String variable) { + return new QHistLocationsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QLocationsHistLocationsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QLocationsHistLocationsLong.java new file mode 100644 index 000000000..30fce36cb --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QLocationsHistLocationsLong.java @@ -0,0 +1,49 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocationsHistLocations; +import java.sql.Types; + +/** + * QLocationsHistLocationsLong is a Querydsl query type for + * QLocationsHistLocationsLong + */ +public class QLocationsHistLocationsLong extends AbstractQLocationsHistLocations, Long> { + + private static final long serialVersionUID = 1713698749; + private static final String TABLE_NAME = "LOCATIONS_HIST_LOCATIONS"; + + public static final QLocationsHistLocationsLong LOCATIONSHISTLOCATIONS = new QLocationsHistLocationsLong(TABLE_NAME); + + public final NumberPath histLocationId = createNumber("histLocationId", Long.class); + + public final NumberPath locationId = createNumber("locationId", Long.class); + + public QLocationsHistLocationsLong(String variable) { + super(QLocationsHistLocationsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(histLocationId, ColumnMetadata.named("HIST_LOCATION_ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(locationId, ColumnMetadata.named("LOCATION_ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + @Override + public NumberPath getLocationId() { + return locationId; + } + + @Override + public NumberPath getHistLocationId() { + return histLocationId; + } + + @Override + public QLocationsHistLocationsLong newWithAlias(String variable) { + return new QLocationsHistLocationsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QLocationsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QLocationsLong.java new file mode 100644 index 000000000..0e1f01392 --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QLocationsLong.java @@ -0,0 +1,51 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocations; +import java.sql.Types; + +/** + * QLocationsLong is a Querydsl query type for QLocationsLong + */ +public class QLocationsLong extends AbstractQLocations, Long> { + + private static final long serialVersionUID = 1565350111; + private static final String TABLE_NAME = "LOCATIONS"; + + public static final QLocationsLong LOCATIONS = new QLocationsLong(TABLE_NAME); + + public final NumberPath genFoiId = createNumber("genFoiId", Long.class); + + public final NumberPath id = createNumber("id", Long.class); + + public QLocationsLong(String variable) { + super(QLocationsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(genFoiId, ColumnMetadata.named("GEN_FOI_ID").ofType(Types.BIGINT).withSize(19)); + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public NumberPath getGenFoiId() { + return genFoiId; + } + + @Override + public QLocationsLong newWithAlias(String variable) { + return new QLocationsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QMultiDatastreamsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QMultiDatastreamsLong.java new file mode 100644 index 000000000..0869ba029 --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QMultiDatastreamsLong.java @@ -0,0 +1,59 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import java.sql.Types; + +/** + * QMultiDatastreamsLong is a Querydsl query type for QMultiDatastreamsLong + */ +public class QMultiDatastreamsLong extends AbstractQMultiDatastreams, Long> { + + private static final long serialVersionUID = -1916297617; + private static final String TABLE_NAME = "MULTI_DATASTREAMS"; + + public static final QMultiDatastreamsLong MULTIDATASTREAMS = new QMultiDatastreamsLong(TABLE_NAME); + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath sensorId = createNumber("sensorId", Long.class); + + public final NumberPath thingId = createNumber("thingId", Long.class); + + public QMultiDatastreamsLong(String variable) { + super(QMultiDatastreamsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + public void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(sensorId, ColumnMetadata.named("SENSOR_ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public NumberPath getThingId() { + return thingId; + } + + @Override + public NumberPath getSensorId() { + return sensorId; + } + + @Override + public QMultiDatastreamsLong newWithAlias(String variable) { + return new QMultiDatastreamsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QMultiDatastreamsObsPropertiesLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QMultiDatastreamsObsPropertiesLong.java new file mode 100644 index 000000000..96acaff9d --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QMultiDatastreamsObsPropertiesLong.java @@ -0,0 +1,49 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreamsObsProperties; +import java.sql.Types; + +/** + * QMultiDatastreamsObsPropertiesLong is a Querydsl query type for + * QMultiDatastreamsObsPropertiesLong + */ +public class QMultiDatastreamsObsPropertiesLong extends AbstractQMultiDatastreamsObsProperties, Long> { + + private static final long serialVersionUID = -838888412; + private static final String TABLE_NAME = "MULTI_DATASTREAMS_OBS_PROPERTIES"; + + public static final QMultiDatastreamsObsPropertiesLong MULTIDATASTREAMSOBSPROPERTIES = new QMultiDatastreamsObsPropertiesLong(TABLE_NAME); + + public final NumberPath multiDatastreamId = createNumber("multiDatastreamId", Long.class); + + public final NumberPath obsPropertyId = createNumber("obsPropertyId", Long.class); + + public QMultiDatastreamsObsPropertiesLong(String variable) { + super(QMultiDatastreamsObsPropertiesLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(multiDatastreamId, ColumnMetadata.named("MULTI_DATASTREAM_ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(obsPropertyId, ColumnMetadata.named("OBS_PROPERTY_ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + @Override + public NumberPath getMultiDatastreamId() { + return multiDatastreamId; + } + + @Override + public NumberPath getObsPropertyId() { + return obsPropertyId; + } + + @Override + public QMultiDatastreamsObsPropertiesLong newWithAlias(String variable) { + return new QMultiDatastreamsObsPropertiesLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QObsPropertiesLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QObsPropertiesLong.java new file mode 100644 index 000000000..e2b717703 --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QObsPropertiesLong.java @@ -0,0 +1,43 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObsProperties; +import java.sql.Types; + +/** + * QObsPropertiesLong is a Querydsl query type for QObsPropertiesLong + */ +public class QObsPropertiesLong extends AbstractQObsProperties, Long> { + + private static final long serialVersionUID = -1131991212; + private static final String TABLE_NAME = "OBS_PROPERTIES"; + + public static final QObsPropertiesLong OBSPROPERTIES = new QObsPropertiesLong(TABLE_NAME); + + public final NumberPath id = createNumber("id", Long.class); + + public QObsPropertiesLong(String variable) { + super(QObsPropertiesLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public QObsPropertiesLong newWithAlias(String variable) { + return new QObsPropertiesLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QObservationsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QObservationsLong.java new file mode 100644 index 000000000..466e83d6b --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QObservationsLong.java @@ -0,0 +1,67 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import java.sql.Types; + +/** + * QObservationsLong is a Querydsl query type for QObservationsLong + */ +public class QObservationsLong extends AbstractQObservations, Long> { + + private static final long serialVersionUID = -1854525274; + private static final String TABLE_NAME = "OBSERVATIONS"; + + public static final QObservationsLong OBSERVATIONS = new QObservationsLong(TABLE_NAME); + + public final NumberPath datastreamId = createNumber("datastreamId", Long.class); + + public final NumberPath featureId = createNumber("featureId", Long.class); + + public final NumberPath id = createNumber("id", Long.class); + + public final NumberPath multiDatastreamId = createNumber("multiDatastreamId", Long.class); + + public QObservationsLong(String variable) { + super(QObservationsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(datastreamId, ColumnMetadata.named("DATASTREAM_ID").ofType(Types.BIGINT).withSize(19)); + addMetadata(featureId, ColumnMetadata.named("FEATURE_ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(multiDatastreamId, ColumnMetadata.named("MULTI_DATASTREAM_ID").ofType(Types.BIGINT).withSize(19)); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public NumberPath getDatastreamId() { + return datastreamId; + } + + @Override + public NumberPath getFeatureId() { + return featureId; + } + + @Override + public NumberPath getMultiDatastreamId() { + return multiDatastreamId; + } + + @Override + public QObservationsLong newWithAlias(String variable) { + return new QObservationsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QSensorsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QSensorsLong.java new file mode 100644 index 000000000..3976d2c2c --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QSensorsLong.java @@ -0,0 +1,43 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQSensors; +import java.sql.Types; + +/** + * QSensorsLong is a Querydsl query type for QSensorsLong + */ +public class QSensorsLong extends AbstractQSensors, Long> { + + private static final long serialVersionUID = 2019004858; + private static final String TABLE_NAME = "SENSORS"; + + public static final QSensorsLong SENSORS = new QSensorsLong(TABLE_NAME); + + public final NumberPath id = createNumber("id", Long.class); + + public QSensorsLong(String variable) { + super(QSensorsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public QSensorsLong newWithAlias(String variable) { + return new QSensorsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QThingsLocationsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QThingsLocationsLong.java new file mode 100644 index 000000000..94a329caf --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QThingsLocationsLong.java @@ -0,0 +1,48 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import java.sql.Types; + +/** + * QThingsLocationsLong is a Querydsl query type for QThingsLocationsLong + */ +public class QThingsLocationsLong extends AbstractQThingsLocations, Long> { + + private static final long serialVersionUID = -1059514278; + private static final String TABLE_NAME = "THINGS_LOCATIONS"; + + public static final QThingsLocationsLong THINGSLOCATIONS = new QThingsLocationsLong(TABLE_NAME); + + public final NumberPath locationId = createNumber("locationId", Long.class); + + public final NumberPath thingId = createNumber("thingId", Long.class); + + public QThingsLocationsLong(String variable) { + super(QThingsLocationsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(locationId, ColumnMetadata.named("LOCATION_ID").ofType(Types.BIGINT).withSize(19).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + @Override + public NumberPath getLocationId() { + return locationId; + } + + @Override + public NumberPath getThingId() { + return thingId; + } + + @Override + public QThingsLocationsLong newWithAlias(String variable) { + return new QThingsLocationsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QThingsLong.java b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QThingsLong.java new file mode 100644 index 000000000..a9e7abdf9 --- /dev/null +++ b/FROST-Server.SQL.PGLong/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/longid/relationalpaths/QThingsLong.java @@ -0,0 +1,43 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThings; +import java.sql.Types; + +/** + * QThingsLong is a Querydsl query type for QThingsLong + */ +public class QThingsLong extends AbstractQThings, Long> { + + private static final long serialVersionUID = -180719772; + private static final String TABLE_NAME = "THINGS"; + + public static final QThingsLong THINGS = new QThingsLong(TABLE_NAME); + + public final NumberPath id = createNumber("id", Long.class); + + public QThingsLong(String variable) { + super(QThingsLong.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BIGINT).withSize(19).notNull()); + } + + /** + * @return the id + */ + @Override + public NumberPath getId() { + return id; + } + + @Override + public QThingsLong newWithAlias(String variable) { + return new QThingsLong(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/pom.xml b/FROST-Server.SQL.PGString/pom.xml index 7598c3e8d..ca2eb8361 100644 --- a/FROST-Server.SQL.PGString/pom.xml +++ b/FROST-Server.SQL.PGString/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT ../pom.xml FROST-Server.SQL.PGString @@ -52,11 +52,6 @@ ${postgis.version} provided - - com.h2database - h2 - ${h2.version} - org.slf4j slf4j-api @@ -156,88 +151,10 @@ - - org.liquibase - liquibase-maven-plugin - ${liquibase.version} - - ${project.basedir}/src/main/resources/liquibase/tablesString.xml - ${builddatabase.driver} - ${builddatabase.url} - ${builddatabase.username} - ${builddatabase.password} - false - - - - generate-sources - - update - - - - - - com.querydsl - querydsl-maven-plugin - ${querydsl.version} - - - - export - - - - - ${builddatabase.driver} - ${builddatabase.url} - ${builddatabase.username} - ${builddatabase.password} - - LOCATIONS_HIST_LOCATIONS,HIST_LOCATIONS,THINGS_LOCATIONS,LOCATIONS,OBSERVATIONS,FEATURES,DATASTREAMS,MULTI_DATASTREAMS,OBS_PROPERTIES,SENSORS,THINGS,MULTI_DATASTREAMS_OBS_PROPERTIES, - locations_hist_locations,hist_locations,things_locations,locations,observations,features,datastreams,multi_datastreams,obs_properties,sensors,things,multi_datastreams_obs_properties - - de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid - ${project.basedir}/target/generated-sources/java - true - - - - javax.annotation - javax.annotation-api - ${annotation-api.version} - - - com.mysema.codegen - codegen - ${codegen.version} - - - com.h2database - h2 - ${h2.version} - - - org.postgresql - postgresql - ${postgres.version} - - - net.postgis - postgis-jdbc - ${postgis.version} - - - ch.qos.logback - logback-classic - 1.1.3 - - - org.apache.maven.plugins maven-compiler-plugin - 3.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/EntityCreator.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/EntityCreator.java deleted file mode 100644 index 4d02407f6..000000000 --- a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/EntityCreator.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.stringid; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.mysema.commons.lang.CloseableIterator; -import com.querydsl.core.Tuple; -import com.querydsl.sql.SQLQuery; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.NavigableElement; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyArrayIndex; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; -import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathVisitor; -import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; -import de.fraunhofer.iosb.ilt.sta.query.Expand; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - */ -class EntityCreator implements ResourcePathVisitor { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(EntityCreator.class); - private final PersistenceManager pm; - private final ResourcePath path; - private final Query query; - private final SQLQuery sqlQuery; - private Object resultObject; - /** - * If resultObject is a property or sub-property, and we are not using - * $value, then the resultObject is encapsulated in a Map, using this key. - */ - private String entityName; - - /** - * - * @param pm The persistence manager. - * @param path The path leading to the items. - * @param query The query parameters to use when fetching expanded items. - * @param sqlQuery The sql query to use for fetching items. - */ - public EntityCreator(PersistenceManager pm, ResourcePath path, Query query, SQLQuery sqlQuery) { - this.pm = pm; - this.path = path; - this.query = query; - this.sqlQuery = sqlQuery; - } - - public Object getEntity() { - return resultObject; - } - - /** - * If resultObject is a property or sub-property, and we are not using - * $value, then the resultObject is encapsulated in a Map, using this key. - * - * @return The name of the resultObject in the map. - */ - public String getEntityName() { - return entityName; - } - - @Override - public void visit(EntityPathElement element) { - sqlQuery.limit(2); - List results = sqlQuery.fetch(); - if (results.size() > 1) { - throw new IllegalStateException("Expecting an element, yet more than 1 result. Got " + results.size() + " results."); - } - if (results.isEmpty()) { - return; - } - - PropertyHelper.entityFromTupleFactory factory = PropertyHelper.getFactoryFor(element.getEntityType().getImplementingClass()); - Entity entity = factory.create(results.get(0), query, new DataSize()); - - if (entity == null) { - throw new IllegalStateException("Failed to create an entity from result set."); - } - expandEntity(entity, query); - resultObject = entity; - } - - private void expandEntity(Entity e, Query query) { - if (query == null) { - return; - } - for (Expand expand : query.getExpand()) { - ResourcePath ePath = new ResourcePath(path.getServiceRootUrl(), null); - ResourcePathElement parentCollection = new EntitySetPathElement(e.getEntityType(), null); - ePath.addPathElement(parentCollection, false, false); - ResourcePathElement parent = new EntityPathElement(e.getId(), e.getEntityType(), parentCollection); - ePath.addPathElement(parent, false, true); - - NavigationProperty firstNp = expand.getPath().get(0); - NavigableElement existing = null; - { - Object o = e.getProperty(firstNp); - if (o instanceof NavigableElement) { - existing = (NavigableElement) o; - } - } - - if (firstNp.isSet) { - EntitySetPathElement child = new EntitySetPathElement(firstNp.type, parent); - ePath.addPathElement(child, true, false); - } else { - EntityPathElement child = new EntityPathElement(null, firstNp.type, parent); - ePath.addPathElement(child, true, false); - } - - Object child; - Query subQuery; - if (expand.getPath().size() == 1) { - // This was the last element in the expand path. The query is for this element. - subQuery = expand.getSubQuery(); - if (subQuery == null) { - subQuery = new Query(query.getSettings()); - } - } else { - // This is not the last element in the expand path. The query is not for this element. - subQuery = new Query(query.getSettings()); - Expand subExpand = new Expand(); - subExpand.getPath().addAll(expand.getPath()); - subExpand.getPath().remove(0); - subExpand.setSubQuery(expand.getSubQuery()); - subQuery.addExpand(subExpand); - if (query.getCount().isPresent()) { - subQuery.setCount(query.isCountOrDefault()); - } - } - - if (existing == null || !existing.isExportObject()) { - child = pm.get(ePath, subQuery); - e.setProperty(firstNp, child); - } else if (existing instanceof EntitySet) { - EntitySet entitySet = (EntitySet) existing; - for (Object subEntity : entitySet) { - if (subEntity instanceof Entity) { - Entity entity = (Entity) subEntity; - expandEntity(entity, subQuery); - } - } - } else if (existing instanceof Entity) { - Entity entity = (Entity) existing; - expandEntity(entity, subQuery); - } - } - } - - @Override - public void visit(EntitySetPathElement element) { - - int top = query.getTopOrDefault(); - sqlQuery.limit(top + 1); - - int skip = 0; - if (query.getSkip().isPresent()) { - skip = query.getSkip().get(); - sqlQuery.offset(skip); - } - long start = System.currentTimeMillis(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Query: {}", sqlQuery.getSQL().getSQL()); - } - CloseableIterator results = sqlQuery.iterate(); //fetch(); - if (LOGGER.isDebugEnabled()) { - long end = System.currentTimeMillis(); - LOGGER.debug("Query executed in {} ms.", end - start); - } - - PropertyHelper.entityFromTupleFactory factory = PropertyHelper.getFactoryFor(element.getEntityType().getImplementingClass()); - EntitySet entitySet = PropertyHelper.createSetFromTuples(factory, results, query, pm.getCoreSettings().getDataSizeMax()); - - if (entitySet == null) { - throw new IllegalStateException("Empty set!"); - } - - if (query.isCountOrDefault()) { - SQLQuery countQuery = sqlQuery.clone(); - countQuery.select(factory.getPrimaryKey()); - int count = (int) countQuery.fetchCount(); - entitySet.setCount(count); - } - - int entityCount = entitySet.size(); - boolean hasMore = results.hasNext(); - if (entityCount < top && hasMore) { - // The loading was aborted, probably due to size constraints. - query.setTop(entityCount); - } - if (hasMore) { - entitySet.setNextLink(UrlHelper.generateNextLink(path, query)); - } - for (Entity e : entitySet) { - expandEntity(e, query); - } - resultObject = entitySet; - } - - @Override - public void visit(PropertyPathElement element) { - element.getParent().visit(this); - if (Entity.class.isAssignableFrom(resultObject.getClass())) { - Object propertyValue = ((Entity) resultObject).getProperty(element.getProperty()); - Map entityMap = new HashMap<>(); - entityName = element.getProperty().name; - entityMap.put(entityName, propertyValue); - resultObject = entityMap; - } - } - - @Override - public void visit(CustomPropertyPathElement element) { - element.getParent().visit(this); - String name = element.getName(); - if (resultObject instanceof Map) { - Map map = (Map) resultObject; - Object inner = map.get(entityName); - if (inner instanceof Map) { - map = (Map) inner; - if (map.containsKey(name)) { - Object propertyValue = map.get(name); - Map entityMap = new HashMap<>(); - entityName = name; - entityMap.put(entityName, propertyValue); - resultObject = entityMap; - return; - } - } - } - - resultObject = null; - entityName = null; - } - - @Override - public void visit(CustomPropertyArrayIndex element) { - element.getParent().visit(this); - int index = element.getIndex(); - if (resultObject instanceof Map) { - Map map = (Map) resultObject; - Object inner = map.get(entityName); - Object propertyValue = null; - if (inner instanceof ArrayNode && ((ArrayNode) inner).size() > index) { - propertyValue = ((ArrayNode) inner).get(index); - } - if (inner instanceof List && ((List) inner).size() > index) { - propertyValue = ((List) inner).get(index); - } - if (propertyValue != null) { - Map entityMap = new HashMap<>(); - entityName = entityName + "[" + Integer.toString(index) + "]"; - entityMap.put(entityName, propertyValue); - resultObject = entityMap; - return; - } - } - - resultObject = null; - entityName = null; - } - -} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/EntityInserter.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/EntityInserter.java deleted file mode 100644 index a755297e3..000000000 --- a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/EntityInserter.java +++ /dev/null @@ -1,1593 +0,0 @@ -/* - * Copyright (C) 2016 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 - * aString with this program. If not, see . - */ -package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.querydsl.core.Tuple; -import com.querydsl.core.dml.StoreClause; -import com.querydsl.core.types.dsl.DateTimePath; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.StringPath; -import com.querydsl.spatial.GeometryPath; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import com.querydsl.sql.dml.SQLInsertClause; -import com.querydsl.sql.dml.SQLUpdateClause; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.geojson.GeoJsonDeserializier; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; -import de.fraunhofer.iosb.ilt.sta.model.builder.DatastreamBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.FeatureOfInterestBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.MultiDatastreamBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.SensorBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.custom.geojson.GeoJsonSerializer; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.model.id.StringId; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.ResultType; -import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; -import java.io.IOException; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import org.geojson.Crs; -import org.geojson.Feature; -import org.geojson.GeoJsonObject; -import org.geojson.jackson.CrsType; -import org.geolatte.common.dataformats.json.jackson.JsonException; -import org.geolatte.common.dataformats.json.jackson.JsonMapper; -import org.geolatte.geom.Geometry; -import org.joda.time.Interval; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author Hylke van der Schaaf - */ -public class EntityInserter { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(EntityInserter.class); - private final PostgresPersistenceManagerString pm; - private ObjectMapper formatter; - - public EntityInserter(PostgresPersistenceManagerString pm) { - this.pm = pm; - } - - public boolean insertDatastream(Datastream ds) throws NoSuchEntityException, IncompleteEntityException { - // First check ObservedPropery, Sensor and Thing - ObservedProperty op = ds.getObservedProperty(); - entityExistsOrCreate(op); - - Sensor s = ds.getSensor(); - entityExistsOrCreate(s); - - Thing t = ds.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - - QDatastreams qd = QDatastreams.datastreams; - SQLInsertClause insert = qFactory.insert(qd); - insert.set(qd.name, ds.getName()); - insert.set(qd.description, ds.getDescription()); - insert.set(qd.observationType, ds.getObservationType()); - insert.set(qd.unitDefinition, ds.getUnitOfMeasurement().getDefinition()); - insert.set(qd.unitName, ds.getUnitOfMeasurement().getName()); - insert.set(qd.unitSymbol, ds.getUnitOfMeasurement().getSymbol()); - insert.set(qd.properties, objectToJson(ds.getProperties())); - - insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManagerString.DATETIME_MAX.getMillis())); - insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManagerString.DATETIME_MIN.getMillis())); - insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManagerString.DATETIME_MAX.getMillis())); - insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManagerString.DATETIME_MIN.getMillis())); - - insert.set(qd.obsPropertyId, (String) op.getId().getValue()); - insert.set(qd.sensorId, (String) s.getId().getValue()); - insert.set(qd.thingId, (String) t.getId().getValue()); - - String datastreamId = insert.executeWithKey(qd.id); - LOGGER.info("Inserted datastream. Created id = {}.", datastreamId); - ds.setId(new StringId(datastreamId)); - - // Create Observations, if any. - for (Observation o : ds.getObservations()) { - o.setDatastream(new DatastreamBuilder().setId(ds.getId()).build()); - o.complete(); - pm.insert(o); - } - - return true; - } - - public boolean updateDatastream(Datastream d, String dsId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QDatastreams qd = QDatastreams.datastreams; - SQLUpdateClause update = qFactory.update(qd); - if (d.isSetName()) { - if (d.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qd.name, d.getName()); - } - if (d.isSetDescription()) { - if (d.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qd.description, d.getDescription()); - } - if (d.isSetObservationType()) { - if (d.getObservationType() == null) { - throw new IncompleteEntityException("observationType can not be null."); - } - update.set(qd.observationType, d.getObservationType()); - } - if (d.isSetProperties()) { - update.set(qd.properties, objectToJson(d.getProperties())); - } - if (d.isSetObservedProperty()) { - if (!entityExists(d.getObservedProperty())) { - throw new NoSuchEntityException("ObservedProperty with no id or not found."); - } - update.set(qd.obsPropertyId, (String) d.getObservedProperty().getId().getValue()); - } - if (d.isSetSensor()) { - if (!entityExists(d.getSensor())) { - throw new NoSuchEntityException("Sensor with no id or not found."); - } - update.set(qd.sensorId, (String) d.getSensor().getId().getValue()); - } - if (d.isSetThing()) { - if (!entityExists(d.getThing())) { - throw new NoSuchEntityException("Thing with no id or not found."); - } - update.set(qd.thingId, (String) d.getThing().getId().getValue()); - } - if (d.isSetUnitOfMeasurement()) { - if (d.getUnitOfMeasurement() == null) { - throw new IncompleteEntityException("unitOfMeasurement can not be null."); - } - UnitOfMeasurement uom = d.getUnitOfMeasurement(); - update.set(qd.unitDefinition, uom.getDefinition()); - update.set(qd.unitName, uom.getName()); - update.set(qd.unitSymbol, uom.getSymbol()); - } - - update.where(qd.id.eq(dsId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Datastream {} caused {} rows to change!", dsId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Observations to the Datastream. - for (Observation o : d.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - String obsId = (String) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.datastreamId, dsId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to Observation {}.", dsId, obsId); - } - } - - LOGGER.debug("Updated Datastream {}", dsId); - return true; - } - - public boolean insertMultiDatastream(MultiDatastream ds) throws NoSuchEntityException, IncompleteEntityException { - // First check Sensor and Thing - Sensor s = ds.getSensor(); - entityExistsOrCreate(s); - - Thing t = ds.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - - QMultiDatastreams qd = QMultiDatastreams.multiDatastreams; - SQLInsertClause insert = qFactory.insert(qd); - insert.set(qd.name, ds.getName()); - insert.set(qd.description, ds.getDescription()); - insert.set(qd.observationTypes, objectToJson(ds.getMultiObservationDataTypes())); - insert.set(qd.unitOfMeasurements, objectToJson(ds.getUnitOfMeasurements())); - insert.set(qd.properties, objectToJson(ds.getProperties())); - - insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManagerString.DATETIME_MAX.getMillis())); - insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManagerString.DATETIME_MIN.getMillis())); - insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManagerString.DATETIME_MAX.getMillis())); - insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManagerString.DATETIME_MIN.getMillis())); - - insert.set(qd.sensorId, (String) s.getId().getValue()); - insert.set(qd.thingId, (String) t.getId().getValue()); - - String multiDatastreamId = insert.executeWithKey(qd.id); - LOGGER.info("Inserted multiDatastream. Created id = {}.", multiDatastreamId); - ds.setId(new StringId(multiDatastreamId)); - - // Create new Locations, if any. - EntitySet ops = ds.getObservedProperties(); - int rank = 0; - for (ObservedProperty op : ops) { - entityExistsOrCreate(op); - String opId = (String) op.getId().getValue(); - - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - insert = qFactory.insert(qMdOp); - insert.set(qMdOp.multiDatastreamId, multiDatastreamId); - insert.set(qMdOp.obsPropertyId, opId); - insert.set(qMdOp.rank, rank); - insert.execute(); - LOGGER.debug("Linked MultiDatastream {} to ObservedProperty {} with rank {}.", multiDatastreamId, opId, rank); - rank++; - } - - // Create Observations, if any. - for (Observation o : ds.getObservations()) { - o.setMultiDatastream(new MultiDatastreamBuilder().setId(ds.getId()).build()); - o.complete(); - pm.insert(o); - } - - return true; - } - - public boolean updateMultiDatastream(MultiDatastream d, String dsId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QMultiDatastreams qd = QMultiDatastreams.multiDatastreams; - SQLUpdateClause update = qFactory.update(qd); - if (d.isSetName()) { - if (d.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qd.name, d.getName()); - } - if (d.isSetDescription()) { - if (d.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qd.description, d.getDescription()); - } - if (d.isSetProperties()) { - update.set(qd.properties, objectToJson(d.getProperties())); - } - - if (d.isSetSensor()) { - if (!entityExists(d.getSensor())) { - throw new NoSuchEntityException("Sensor with no id or not found."); - } - update.set(qd.sensorId, (String) d.getSensor().getId().getValue()); - } - if (d.isSetThing()) { - if (!entityExists(d.getThing())) { - throw new NoSuchEntityException("Thing with no id or not found."); - } - update.set(qd.thingId, (String) d.getThing().getId().getValue()); - } - - MultiDatastream original = (MultiDatastream) pm.getEntityById(null, EntityType.MultiDatastream, new StringId(dsId)); - int countOrig = original.getMultiObservationDataTypes().size(); - - int countUom = countOrig; - if (d.isSetUnitOfMeasurements()) { - if (d.getUnitOfMeasurements() == null) { - throw new IncompleteEntityException("unitOfMeasurements can not be null."); - } - List uoms = d.getUnitOfMeasurements(); - countUom = uoms.size(); - update.set(qd.unitOfMeasurements, objectToJson(uoms)); - } - int countDataTypes = countOrig; - if (d.isSetMultiObservationDataTypes()) { - List dataTypes = d.getMultiObservationDataTypes(); - if (dataTypes == null) { - throw new IncompleteEntityException("multiObservationDataTypes can not be null."); - } - countDataTypes = dataTypes.size(); - update.set(qd.observationTypes, objectToJson(dataTypes)); - } - EntitySet ops = d.getObservedProperties(); - int countOps = countOrig + ops.size(); - for (ObservedProperty op : ops) { - if (op.getId() == null || !entityExists(op)) { - throw new NoSuchEntityException("ObservedProperty with no id or not found."); - } - } - - if (countUom != countDataTypes) { - throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of multiObservationDataTypes."); - } - if (countUom != countOps) { - throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of ObservedProperties."); - } - - update.where(qd.id.eq(dsId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Datastream {} caused {} rows to change!", dsId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing ObservedProperties to the MultiDatastream. - int rank = countOrig; - for (ObservedProperty op : ops) { - String opId = (String) op.getId().getValue(); - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - long oCount = qFactory.insert(qMdOp) - .set(qMdOp.multiDatastreamId, dsId) - .set(qMdOp.obsPropertyId, opId) - .set(qMdOp.rank, rank) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to ObservedProperty {} with rank {}.", dsId, opId, rank); - } - rank++; - } - - // Link existing Observations to the MultiDatastream. - for (Observation o : d.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - String obsId = (String) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.datastreamId, dsId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to Observation {}.", dsId, obsId); - } - } - - LOGGER.debug("Updated Datastream {}", dsId); - return true; - } - - public boolean insertFeatureOfInterest(FeatureOfInterest foi) throws NoSuchEntityException { - // No linked entities to check first. - SQLQueryFactory qFactory = pm.createQueryFactory(); - QFeatures qfoi = QFeatures.features; - SQLInsertClause insert = qFactory.insert(qfoi); - insert.set(qfoi.name, foi.getName()); - insert.set(qfoi.description, foi.getDescription()); - insert.set(qfoi.properties, objectToJson(foi.getProperties())); - - String encodingType = foi.getEncodingType(); - insert.set(qfoi.encodingType, encodingType); - insertGeometry(insert, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); - - String generatedId = insert.executeWithKey(qfoi.id); - LOGGER.info("Inserted FeatureOfInterest. Created id = {}.", generatedId); - foi.setId(new StringId(generatedId)); - return true; - } - - public boolean updateFeatureOfInterest(FeatureOfInterest foi, String foiId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QFeatures qfoi = QFeatures.features; - SQLUpdateClause update = qFactory.update(qfoi); - if (foi.isSetName()) { - if (foi.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qfoi.name, foi.getName()); - } - if (foi.isSetDescription()) { - if (foi.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qfoi.description, foi.getDescription()); - } - if (foi.isSetProperties()) { - update.set(qfoi.properties, objectToJson(foi.getProperties())); - } - - if (foi.isSetEncodingType() && foi.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - if (foi.isSetFeature() && foi.getFeature() == null) { - throw new IncompleteEntityException("feature can not be null."); - } - if (foi.isSetEncodingType() && foi.getEncodingType() != null && foi.isSetFeature() && foi.getFeature() != null) { - String encodingType = foi.getEncodingType(); - update.set(qfoi.encodingType, encodingType); - insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); - } else if (foi.isSetEncodingType() && foi.getEncodingType() != null) { - String encodingType = foi.getEncodingType(); - update.set(qfoi.encodingType, encodingType); - } else if (foi.isSetFeature() && foi.getFeature() != null) { - String encodingType = qFactory.select(qfoi.encodingType) - .from(qfoi) - .where(qfoi.id.eq(foiId)) - .fetchFirst(); - Object parsedObject = reParseGeometry(encodingType, foi.getFeature()); - insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, parsedObject); - } - - update.where(qfoi.id.eq(foiId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating FeatureOfInterest {} caused {} rows to change!", foiId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Observations to the FeatureOfInterest. - for (Observation o : foi.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - String obsId = (String) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.featureId, foiId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned FeatureOfInterest {} to Observation {}.", foiId, obsId); - } - } - - LOGGER.debug("Updated FeatureOfInterest {}", foiId); - return true; - } - - public FeatureOfInterest generateFeatureOfInterest(Id datastreamId, boolean isMultiDatastream) throws NoSuchEntityException { - String dsId = (String) datastreamId.getValue(); - SQLQueryFactory qf = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - QThingsLocations qtl = QThingsLocations.thingsLocations; - QThings qt = QThings.things; - QDatastreams qd = QDatastreams.datastreams; - QMultiDatastreams qmd = QMultiDatastreams.multiDatastreams; - // TODO: Should probably contain a where that only returns locations - // with a supported encoding type. - SQLQuery query = qf.select(ql.id, ql.genFoiId) - .from(ql) - .innerJoin(qtl).on(ql.id.eq(qtl.locationId)) - .innerJoin(qt).on(qt.id.eq(qtl.thingId)); - if (isMultiDatastream) { - query.innerJoin(qmd).on(qmd.thingId.eq(qt.id)) - .where(qmd.id.eq(dsId)); - } else { - query.innerJoin(qd).on(qd.thingId.eq(qt.id)) - .where(qd.id.eq(dsId)); - } - Tuple tuple = query.fetchOne(); - if (tuple == null) { - // Can not generate foi from Thing with no locations. - throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); - } - String genFoiId = tuple.get(ql.genFoiId); - String locationId = tuple.get(ql.id); - - FeatureOfInterest foi; - if (genFoiId == null) { - query = qf.select(ql.id, ql.encodingType, ql.location) - .from(ql) - .where(ql.id.eq(locationId)); - tuple = query.fetchOne(); - if (tuple == null) { - // Can not generate foi from Thing with no locations. - // Should not happen, since the query succeeded just before. - throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); - } - String encoding = tuple.get(ql.encodingType); - String locString = tuple.get(ql.location); - Object locObject = PropertyHelper.locationFromEncoding(encoding, locString); - foi = new FeatureOfInterestBuilder() - .setName("FoI for location " + locationId) - .setDescription("Generated from location " + locationId) - .setEncodingType(encoding) - .setFeature(locObject) - .build(); - insertFeatureOfInterest(foi); - String foiId = (String) foi.getId().getValue(); - qf.update(ql) - .set(ql.genFoiId, (String) foi.getId().getValue()) - .where(ql.id.eq(locationId)) - .execute(); - LOGGER.debug("Generated foi {} from Location {}.", foiId, locationId); - } else { - foi = new FeatureOfInterest(); - foi.setId(new StringId(genFoiId)); - } - return foi; - } - - public boolean insertHistoricalLocation(HistoricalLocation h) throws NoSuchEntityException, IncompleteEntityException { - Thing t = h.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QHistLocations qhl = QHistLocations.histLocations; - SQLInsertClause insert = qFactory.insert(qhl); - insert.set(qhl.time, new Timestamp(h.getTime().getDateTime().getMillis())); - insert.set(qhl.thingId, (String) h.getThing().getId().getValue()); - - String generatedId = insert.executeWithKey(qhl.id); - LOGGER.info("Inserted HistoricalLocation. Created id = {}.", generatedId); - h.setId(new StringId(generatedId)); - - EntitySet locations = h.getLocations(); - for (Location l : locations) { - entityExistsOrCreate(l); - String lId = (String) l.getId().getValue(); - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, generatedId); - insert.set(qlhl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", lId, generatedId); - } - return true; - } - - public boolean updateHistoricalLocation(HistoricalLocation hl, String id) { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QHistLocations qhl = QHistLocations.histLocations; - SQLUpdateClause update = qFactory.update(qhl); - if (hl.isSetThing()) { - if (!entityExists(hl.getThing())) { - throw new IncompleteEntityException("Thing can not be null."); - } - update.set(qhl.thingId, (String) hl.getThing().getId().getValue()); - } - if (hl.isSetTime()) { - if (hl.getTime() == null) { - throw new IncompleteEntityException("time can not be null."); - } - insertTimeInstant(update, qhl.time, hl.getTime()); - } - update.where(qhl.id.eq(id)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Location {} caused {} rows to change!", id, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Location {}", id); - - // Link existing locations to the HistoricalLocation. - for (Location l : hl.getLocations()) { - if (!entityExists(l)) { - throw new IllegalArgumentException("Unknown Location or Location with no id."); - } - String lId = (String) l.getId().getValue(); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - SQLInsertClause insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, id); - insert.set(qlhl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", lId, id); - } - return true; - } - - public boolean insertLocation(Location l) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - SQLInsertClause insert = qFactory.insert(ql); - insert.set(ql.name, l.getName()); - insert.set(ql.description, l.getDescription()); - insert.set(ql.properties, objectToJson(l.getProperties())); - - String encodingType = l.getEncodingType(); - insert.set(ql.encodingType, encodingType); - insertGeometry(insert, ql.location, ql.geom, encodingType, l.getLocation()); - - String locationId = insert.executeWithKey(ql.id); - LOGGER.info("Inserted Location. Created id = {}.", locationId); - l.setId(new StringId(locationId)); - - // Link Things - EntitySet things = l.getThings(); - for (Thing t : things) { - entityExistsOrCreate(t); - String thingId = (String) t.getId().getValue(); - - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - long count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new Location to thing. - insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - - // Create HistoricalLocation for Thing - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - String histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - // Link Location to HistoricalLocation. - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locationId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locationId, histLocationId); - } - - return true; - } - - public boolean updateLocation(Location l, String locationId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - SQLUpdateClause update = qFactory.update(ql); - if (l.isSetName()) { - if (l.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(ql.name, l.getName()); - } - if (l.isSetDescription()) { - if (l.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(ql.description, l.getDescription()); - } - if (l.isSetProperties()) { - update.set(ql.properties, objectToJson(l.getProperties())); - } - - if (l.isSetEncodingType() && l.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - if (l.isSetLocation() && l.getLocation() == null) { - throw new IncompleteEntityException("locations can not be null."); - } - if (l.isSetEncodingType() && l.getEncodingType() != null && l.isSetLocation() && l.getLocation() != null) { - String encodingType = l.getEncodingType(); - update.set(ql.encodingType, encodingType); - insertGeometry(update, ql.location, ql.geom, encodingType, l.getLocation()); - } else if (l.isSetEncodingType() && l.getEncodingType() != null) { - String encodingType = l.getEncodingType(); - update.set(ql.encodingType, encodingType); - } else if (l.isSetLocation() && l.getLocation() != null) { - String encodingType = qFactory.select(ql.encodingType) - .from(ql) - .where(ql.id.eq(locationId)) - .fetchFirst(); - Object parsedObject = reParseGeometry(encodingType, l.getLocation()); - insertGeometry(update, ql.location, ql.geom, encodingType, parsedObject); - } - - update.where(ql.id.eq(locationId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Location {} caused {} rows to change!", locationId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Location {}", locationId); - - // Link HistoricalLocation. - for (HistoricalLocation hl : l.getHistoricalLocations()) { - if (hl.getId() == null) { - throw new IllegalArgumentException("HistoricalLocation with no id."); - } - String hlId = (String) hl.getId().getValue(); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - SQLInsertClause insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, hlId); - insert.set(qlhl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", locationId, hlId); - } - - // Link Things - EntitySet things = l.getThings(); - for (Thing t : things) { - if (!entityExists(t)) { - throw new NoSuchEntityException("Thing not found."); - } - String thingId = (String) t.getId().getValue(); - - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new Location to thing. - SQLInsertClause insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - - // Create HistoricalLocation for Thing - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - String histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - // Link Location to HistoricalLocation. - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locationId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locationId, histLocationId); - } - return true; - } - - public boolean insertObservation(Observation o) throws NoSuchEntityException, IncompleteEntityException { - Datastream ds = o.getDatastream(); - MultiDatastream mds = o.getMultiDatastream(); - Id streamId; - boolean isMultiDatastream = false; - if (ds != null) { - entityExistsOrCreate(ds); - streamId = ds.getId(); - } else if (mds != null) { - entityExistsOrCreate(mds); - streamId = mds.getId(); - isMultiDatastream = true; - } else { - throw new IncompleteEntityException("Missing Datastream or MultiDatastream."); - } - - FeatureOfInterest f = o.getFeatureOfInterest(); - if (f == null) { - f = generateFeatureOfInterest(streamId, isMultiDatastream); - } else { - entityExistsOrCreate(f); - } - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObservations qo = QObservations.observations; - SQLInsertClause insert = qFactory.insert(qo); - - insert.set(qo.parameters, objectToJson(o.getParameters())); - TimeValue phenomenonTime = o.getPhenomenonTime(); - if (phenomenonTime == null) { - phenomenonTime = TimeInstant.now(); - } - insertTimeValue(insert, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, phenomenonTime); - insertTimeInstant(insert, qo.resultTime, o.getResultTime()); - insertTimeInterval(insert, qo.validTimeStart, qo.validTimeEnd, o.getValidTime()); - - Object result = o.getResult(); - if (isMultiDatastream) { - if (!(result instanceof List)) { - throw new IllegalArgumentException("Multidatastream only accepts array results."); - } - List list = (List) result; - ResourcePath path = mds.getPath(); - path.addPathElement(new EntitySetPathElement(EntityType.ObservedProperty, null), false, false); - long count = pm.count(path, null); - if (count != list.size()) { - throw new IllegalArgumentException("Size of result array (" + list.size() + ") must match number of observed properties (" + count + ") in the MultiDatastream."); - } - } - - if (result instanceof Number) { - insert.set(qo.resultType, ResultType.NUMBER.sqlValue()); - insert.set(qo.resultString, result.toString()); - insert.set(qo.resultNumber, ((Number) result).doubleValue()); - } else if (result instanceof Boolean) { - insert.set(qo.resultType, ResultType.BOOLEAN.sqlValue()); - insert.set(qo.resultString, result.toString()); - insert.set(qo.resultBoolean, (Boolean) result); - } else if (result instanceof String) { - insert.set(qo.resultType, ResultType.STRING.sqlValue()); - insert.set(qo.resultString, result.toString()); - } else { - insert.set(qo.resultType, ResultType.OBJECT_ARRAY.sqlValue()); - insert.set(qo.resultJson, objectToJson(result)); - } - - if (o.getResultQuality() != null) { - insert.set(qo.resultQuality, o.getResultQuality().toString()); - } - if (ds != null) { - insert.set(qo.datastreamId, (String) ds.getId().getValue()); - } - if (mds != null) { - insert.set(qo.multiDatastreamId, (String) mds.getId().getValue()); - } - insert.set(qo.featureId, (String) f.getId().getValue()); - - String generatedId = insert.executeWithKey(qo.id); - LOGGER.debug("Inserted Observation. Created id = {}.", generatedId); - o.setId(new StringId(generatedId)); - return true; - } - - public boolean updateObservation(Observation o, String id) { - Observation oldObservation = (Observation) pm.getEntityById(null, EntityType.Observation, new StringId(id)); - Datastream ds = oldObservation.getDatastream(); - MultiDatastream mds = oldObservation.getMultiDatastream(); - boolean newHasDatastream = ds != null; - boolean newHasMultiDatastream = mds != null; - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObservations qo = QObservations.observations; - SQLUpdateClause update = qFactory.update(qo); - if (o.isSetDatastream()) { - if (o.getDatastream() == null) { - newHasDatastream = false; - update.setNull(qo.datastreamId); - } else { - if (!entityExists(o.getDatastream())) { - throw new IncompleteEntityException("Datastream not found."); - } - newHasDatastream = true; - ds = o.getDatastream(); - update.set(qo.datastreamId, (String) ds.getId().getValue()); - } - } - if (o.isSetMultiDatastream()) { - mds = o.getMultiDatastream(); - if (mds == null) { - newHasMultiDatastream = false; - update.setNull(qo.multiDatastreamId); - } else { - if (!entityExists(mds)) { - throw new IncompleteEntityException("MultiDatastream not found."); - } - newHasMultiDatastream = true; - update.set(qo.multiDatastreamId, (String) mds.getId().getValue()); - } - } - if (newHasDatastream == newHasMultiDatastream) { - throw new IllegalArgumentException("Observation must have either a Datastream or a MultiDatastream."); - } - if (o.isSetFeatureOfInterest()) { - if (!entityExists(o.getFeatureOfInterest())) { - throw new IncompleteEntityException("FeatureOfInterest not found."); - } - update.set(qo.featureId, (String) o.getFeatureOfInterest().getId().getValue()); - } - if (o.isSetParameters()) { - update.set(qo.parameters, objectToJson(o.getParameters())); - } - if (o.isSetPhenomenonTime()) { - if (o.getPhenomenonTime() == null) { - throw new IncompleteEntityException("phenomenonTime can not be null."); - } - insertTimeValue(update, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, o.getPhenomenonTime()); - } - - if (o.isSetResult() && o.getResult() != null) { - Object result = o.getResult(); - if (newHasMultiDatastream) { - if (!(result instanceof List)) { - throw new IllegalArgumentException("Multidatastream only accepts array results."); - } - List list = (List) result; - ResourcePath path = mds.getPath(); - path.addPathElement(new EntitySetPathElement(EntityType.ObservedProperty, null), false, false); - long count = pm.count(path, null); - if (count != list.size()) { - throw new IllegalArgumentException("Size of result array (" + list.size() + ") must match number of observed properties (" + count + ") in the MultiDatastream."); - } - } - if (result instanceof Number) { - update.set(qo.resultType, ResultType.NUMBER.sqlValue()); - update.set(qo.resultString, result.toString()); - update.set(qo.resultNumber, ((Number) result).doubleValue()); - update.setNull(qo.resultBoolean); - update.setNull(qo.resultJson); - } else if (result instanceof Boolean) { - update.set(qo.resultType, ResultType.BOOLEAN.sqlValue()); - update.set(qo.resultString, result.toString()); - update.set(qo.resultBoolean, (Boolean) result); - update.setNull(qo.resultNumber); - update.setNull(qo.resultJson); - } else if (result instanceof String) { - update.set(qo.resultType, ResultType.STRING.sqlValue()); - update.set(qo.resultString, result.toString()); - update.setNull(qo.resultNumber); - update.setNull(qo.resultBoolean); - update.setNull(qo.resultJson); - } else { - update.set(qo.resultType, ResultType.OBJECT_ARRAY.sqlValue()); - update.set(qo.resultJson, objectToJson(result)); - update.setNull(qo.resultString); - update.setNull(qo.resultNumber); - update.setNull(qo.resultBoolean); - } - } - - if (o.isSetResultQuality()) { - update.set(qo.resultQuality, objectToJson(o.getResultQuality())); - } - if (o.isSetResultTime()) { - insertTimeInstant(update, qo.resultTime, o.getResultTime()); - } - if (o.isSetValidTime()) { - insertTimeInterval(update, qo.validTimeStart, qo.validTimeEnd, o.getValidTime()); - } - update.where(qo.id.eq(id)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Observation {} caused {} rows to change!", id, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Observation {}", id); - return true; - } - - public boolean insertObservedProperty(ObservedProperty op) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObsProperties qop = QObsProperties.obsProperties; - SQLInsertClause insert = qFactory.insert(qop); - insert.set(qop.definition, op.getDefinition()); - insert.set(qop.name, op.getName()); - insert.set(qop.description, op.getDescription()); - insert.set(qop.properties, objectToJson(op.getProperties())); - - String generatedId = insert.executeWithKey(qop.id); - LOGGER.info("Inserted ObservedProperty. Created id = {}.", generatedId); - op.setId(new StringId(generatedId)); - - // Create new datastreams, if any. - for (Datastream ds : op.getDatastreams()) { - ds.setSensor(new SensorBuilder().setId(op.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : op.getMultiDatastreams()) { - mds.setSensor(new SensorBuilder().setId(op.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - return true; - } - - public boolean updateObservedProperty(ObservedProperty op, String opId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObsProperties qop = QObsProperties.obsProperties; - SQLUpdateClause update = qFactory.update(qop); - if (op.isSetDefinition()) { - if (op.getDefinition() == null) { - throw new IncompleteEntityException("definition can not be null."); - } - update.set(qop.definition, op.getDefinition()); - } - if (op.isSetDescription()) { - if (op.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qop.description, op.getDescription()); - } - if (op.isSetName()) { - if (op.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qop.name, op.getName()); - } - if (op.isSetProperties()) { - update.set(qop.properties, objectToJson(op.getProperties())); - } - - update.where(qop.id.eq(opId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating ObservedProperty {} caused {} rows to change!", opId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Datastreams to the observedProperty. - for (Datastream ds : op.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("ObservedProperty with no id or non existing."); - } - String dsId = (String) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.obsPropertyId, opId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to ObservedProperty {}.", dsId, opId); - } - } - - if (!op.getMultiDatastreams().isEmpty()) { - throw new IllegalArgumentException("Can not add MultiDatastreams to an ObservedProperty."); - } - - LOGGER.debug("Updated ObservedProperty {}", opId); - return true; - } - - public boolean insertSensor(Sensor s) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QSensors qs = QSensors.sensors; - SQLInsertClause insert = qFactory.insert(qs); - insert.set(qs.name, s.getName()); - insert.set(qs.description, s.getDescription()); - insert.set(qs.encodingType, s.getEncodingType()); - // TODO: Check metadata serialisation. - insert.set(qs.metadata, s.getMetadata().toString()); - insert.set(qs.properties, objectToJson(s.getProperties())); - - String generatedId = insert.executeWithKey(qs.id); - LOGGER.info("Inserted Sensor. Created id = {}.", generatedId); - s.setId(new StringId(generatedId)); - - // Create new datastreams, if any. - for (Datastream ds : s.getDatastreams()) { - ds.setSensor(new SensorBuilder().setId(s.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : s.getMultiDatastreams()) { - mds.setSensor(new SensorBuilder().setId(s.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - return true; - } - - public boolean updateSensor(Sensor s, String sensorId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QSensors qs = QSensors.sensors; - SQLUpdateClause update = qFactory.update(qs); - if (s.isSetName()) { - if (s.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qs.name, s.getName()); - } - if (s.isSetDescription()) { - if (s.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qs.description, s.getDescription()); - } - if (s.isSetEncodingType()) { - if (s.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - update.set(qs.encodingType, s.getEncodingType()); - } - if (s.isSetMetadata()) { - if (s.getMetadata() == null) { - throw new IncompleteEntityException("metadata can not be null."); - } - // TODO: Check metadata serialisation. - update.set(qs.metadata, s.getMetadata().toString()); - } - if (s.isSetProperties()) { - update.set(qs.properties, objectToJson(s.getProperties())); - } - - update.where(qs.id.eq(sensorId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Sensor {} caused {} rows to change!", sensorId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Datastreams to the sensor. - for (Datastream ds : s.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("Datastream with no id or non existing."); - } - String dsId = (String) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.sensorId, sensorId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to sensor {}.", dsId, sensorId); - } - } - - // Link existing MultiDatastreams to the sensor. - for (MultiDatastream mds : s.getMultiDatastreams()) { - if (mds.getId() == null || !entityExists(mds)) { - throw new NoSuchEntityException("MultiDatastream with no id or non existing."); - } - String mdsId = (String) mds.getId().getValue(); - QMultiDatastreams qmds = QMultiDatastreams.multiDatastreams; - long mdsCount = qFactory.update(qmds) - .set(qmds.sensorId, sensorId) - .where(qmds.id.eq(mdsId)) - .execute(); - if (mdsCount > 0) { - LOGGER.info("Assigned multiDatastream {} to sensor {}.", mdsId, sensorId); - } - } - - LOGGER.debug("Updated Sensor {}", sensorId); - return true; - } - - public boolean insertThing(Thing t) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QThings qt = QThings.things; - SQLInsertClause insert = qFactory.insert(qt); - insert.set(qt.name, t.getName()); - insert.set(qt.description, t.getDescription()); - insert.set(qt.properties, objectToJson(t.getProperties())); - - String thingId = insert.executeWithKey(qt.id); - LOGGER.info("Inserted Thing. Created id = {}.", thingId); - t.setId(new StringId(thingId)); - - // Create new Locations, if any. - List locationIds = new ArrayList<>(); - for (Location l : t.getLocations()) { - entityExistsOrCreate(l); - String lId = (String) l.getId().getValue(); - - QThingsLocations qtl = QThingsLocations.thingsLocations; - insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", lId, thingId); - locationIds.add(lId); - } - - // Now link the new locations also to a historicalLocation. - if (!locationIds.isEmpty()) { - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - String histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - for (String locId : locationIds) { - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locId, histLocationId); - } - } - - // Create new datastreams, if any. - for (Datastream ds : t.getDatastreams()) { - ds.setThing(new ThingBuilder().setId(t.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : t.getMultiDatastreams()) { - mds.setThing(new ThingBuilder().setId(t.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - // TODO: if we allow the creation of historicalLocations through Things - // then we have to be able to link those to Locations we might have just created. - // However, id juggling will be needed! - return true; - } - - public boolean updateThing(Thing t, String thingId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QThings qt = QThings.things; - SQLUpdateClause update = qFactory.update(qt); - if (t.isSetName()) { - if (t.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qt.name, t.getName()); - } - if (t.isSetDescription()) { - if (t.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qt.description, t.getDescription()); - } - if (t.isSetProperties()) { - update.set(qt.properties, objectToJson(t.getProperties())); - } - update.where(qt.id.eq(thingId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Thing {} caused {} rows to change!", thingId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Thing {}", thingId); - - // Link existing Datastreams to the thing. - for (Datastream ds : t.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("Datastream with no id or non existing."); - } - String dsId = (String) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.thingId, thingId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to thing {}.", dsId, thingId); - } - } - - // Link existing MultiDatastreams to the thing. - for (MultiDatastream mds : t.getMultiDatastreams()) { - if (mds.getId() == null || !entityExists(mds)) { - throw new NoSuchEntityException("MultiDatastream with no id or non existing."); - } - String mdsId = (String) mds.getId().getValue(); - QMultiDatastreams qmds = QMultiDatastreams.multiDatastreams; - long mdsCount = qFactory.update(qmds) - .set(qmds.thingId, thingId) - .where(qmds.id.eq(mdsId)) - .execute(); - if (mdsCount > 0) { - LOGGER.info("Assigned multiDatastream {} to thing {}.", mdsId, thingId); - } - } - - // Link existing locations to the thing. - if (!t.getLocations().isEmpty()) { - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new locations to Thing, track the ids. - List locationIds = new ArrayList<>(); - for (Location l : t.getLocations()) { - if (l.getId() == null || !entityExists(l)) { - throw new NoSuchEntityException("Location with no id."); - } - String locationId = (String) l.getId().getValue(); - - SQLInsertClause insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - locationIds.add(locationId); - } - - // Now link the newly linked locations also to a historicalLocation. - if (!locationIds.isEmpty()) { - QHistLocations qhl = QHistLocations.histLocations; - SQLInsertClause insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - String histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - for (String locId : locationIds) { - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locId, histLocationId); - } - } - } - return true; - } - - private static T insertTimeValue(T clause, DateTimePath startPath, DateTimePath endPath, TimeValue time) { - if (time instanceof TimeInstant) { - TimeInstant timeInstant = (TimeInstant) time; - insertTimeInstant(clause, endPath, timeInstant); - return insertTimeInstant(clause, startPath, timeInstant); - } else if (time instanceof TimeInterval) { - TimeInterval timeInterval = (TimeInterval) time; - return insertTimeInterval(clause, startPath, endPath, timeInterval); - } - return clause; - } - - private static T insertTimeInstant(T clause, DateTimePath path, TimeInstant time) { - if (time == null) { - return clause; - } - clause.set(path, new Timestamp(time.getDateTime().getMillis())); - return clause; - } - - private static T insertTimeInterval(T clause, DateTimePath startPath, DateTimePath endPath, TimeInterval time) { - if (time == null) { - return clause; - } - Interval interval = time.getInterval(); - clause.set(startPath, new Timestamp(interval.getStartMillis())); - clause.set(endPath, new Timestamp(interval.getEndMillis())); - return clause; - } - - /** - * Sets both the geometry and location in the clause. - * - * @param The type of the clause. - * @param clause The insert or update clause to add to. - * @param locationPath The path to the location column. - * @param geomPath The path to the geometry column. - * @param encodingType The encoding type. - * @param location The location. - * @return The insert or update clause. - */ - private T insertGeometry(T clause, StringPath locationPath, GeometryPath geomPath, String encodingType, final Object location) { - if (encodingType != null && GeoJsonDeserializier.encodings.contains(encodingType.toLowerCase())) { - String locJson; - try { - locJson = new GeoJsonSerializer().serialize(location); - } catch (JsonProcessingException ex) { - LOGGER.error("Failed to store.", ex); - throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); - } - - // Postgres does not support Feature. - Object geoLocation = location; - if (location instanceof Feature) { - geoLocation = ((Feature) location).getGeometry(); - } - // Ensure the geoJson has a crs, otherwise Postgres complains. - if (geoLocation instanceof GeoJsonObject) { - GeoJsonObject geoJsonObject = (GeoJsonObject) geoLocation; - Crs crs = geoJsonObject.getCrs(); - if (crs == null) { - crs = new Crs(); - crs.setType(CrsType.name); - crs.getProperties().put("name", "EPSG:4326"); - geoJsonObject.setCrs(crs); - } - } - String geoJson; - try { - geoJson = new GeoJsonSerializer().serialize(geoLocation); - } catch (JsonProcessingException ex) { - LOGGER.error("Failed to store.", ex); - throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); - } - - try { - // geojson.jackson allows invalid polygons, geolatte catches those. - new JsonMapper().fromJson(geoJson, Geometry.class); - } catch (JsonException ex) { - throw new IllegalArgumentException("Invalid geoJson: " + ex.getMessage()); - } - clause.set(geomPath, Expressions.template(Geometry.class, "ST_Force2D(ST_Transform(ST_GeomFromGeoJSON({0}), 4326))", geoJson)); - clause.set(locationPath, locJson); - } else { - String json; - json = objectToJson(location); - clause.setNull(geomPath); - clause.set(locationPath, json); - } - return clause; - } - - private Object reParseGeometry(String encodingType, Object object) { - String json = objectToJson(object); - return PropertyHelper.locationFromEncoding(encodingType, json); - } - - /** - * Throws an exception if the entity has an id, but does not exist or if the - * entity can not be created. - * - * @param pm the persistenceManager - * @param e The Entity to check. - * @throws NoSuchEntityException If the entity has an id, but does not - * exist. - * @throws IncompleteEntityException If the entity has no id, but is not - * complete and can thus not be created. - */ - private void entityExistsOrCreate(Entity e) throws NoSuchEntityException, IncompleteEntityException { - if (e != null && e.getId() == null) { - e.complete(); - pm.insert(e); - } else if (e == null || !entityExists(e)) { - if (e == null) { - throw new NoSuchEntityException("No entity!"); - } - throw new NoSuchEntityException("No such entity '" + e.getEntityType() + "' with id " + e.getId().getValue()); - } - } - - public boolean entityExists(Entity e) { - if (e == null || e.getId() == null) { - return false; - } - String id = (String) e.getId().getValue(); - SQLQueryFactory qFactory = pm.createQueryFactory(); - long count = 0; - switch (e.getEntityType()) { - case Datastream: - QDatastreams d = QDatastreams.datastreams; - count = qFactory.select() - .from(d) - .where(d.id.eq(id)) - .fetchCount(); - break; - - case MultiDatastream: - QMultiDatastreams md = QMultiDatastreams.multiDatastreams; - count = qFactory.select() - .from(md) - .where(md.id.eq(id)) - .fetchCount(); - break; - - case FeatureOfInterest: - QFeatures foi = QFeatures.features; - count = qFactory.select() - .from(foi) - .where(foi.id.eq(id)) - .fetchCount(); - break; - - case HistoricalLocation: - QHistLocations h = QHistLocations.histLocations; - count = qFactory.select() - .from(h) - .where(h.id.eq(id)) - .fetchCount(); - break; - - case Location: - QLocations l = QLocations.locations; - count = qFactory.select() - .from(l) - .where(l.id.eq(id)) - .fetchCount(); - break; - - case Observation: - QObservations o = QObservations.observations; - count = qFactory.select() - .from(o) - .where(o.id.eq(id)) - .fetchCount(); - break; - - case ObservedProperty: - QObsProperties op = QObsProperties.obsProperties; - count = qFactory.select() - .from(op) - .where(op.id.eq(id)) - .fetchCount(); - break; - - case Sensor: - QSensors s = QSensors.sensors; - count = qFactory.select() - .from(s) - .where(s.id.eq(id)) - .fetchCount(); - break; - - case Thing: - QThings t = QThings.things; - count = qFactory.select() - .from(t) - .where(t.id.eq(id)) - .fetchCount(); - break; - - default: - throw new AssertionError(e.getEntityType().name()); - } - if (count > 1) { - LOGGER.error("More than one instance of {} with id {}.", e.getEntityType(), id); - } - return count > 0; - } - - public boolean entityExists(ResourcePath path) { - long count = pm.count(path, null); - if (count > 1) { - LOGGER.error("More than one instance of {}", path.toString()); - } - return count > 0; - } - - public String objectToJson(Object object) { - if (object == null) { - return null; - } - try { - return getFormatter().writeValueAsString(object); - } catch (IOException ex) { - throw new IllegalStateException("Could not serialise object.", ex); - } - } - - public ObjectMapper getFormatter() { - if (formatter == null) { - formatter = new ObjectMapper(); - } - return formatter; - } - -} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/IdGenerationHandlerString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/IdGenerationHandlerString.java new file mode 100644 index 000000000..d0f6c1b31 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/IdGenerationHandlerString.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * Copyright (C) 2018 KIT TECO, Vincenz-Prießnitz-Str. 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.sta.persistence.postgres.stringid; + +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.IdGenerationHandler; + +/** + * + * Class for handling the persistence setting "idGenerationMode". + * + * @author koepke, scf + */ +public class IdGenerationHandlerString extends IdGenerationHandler { + + /** + * Constructor for IdGenerationHandler. + * + * @param entity Entity for which idGenerationMode should be + * checked/applied. + */ + public IdGenerationHandlerString(Entity entity) { + super(entity); + } + + /** + * + * Modify the entity id. + * + */ + @Override + public void modifyClientSuppliedId() { + // Nothing to do for now. + } + + /** + * + * Checks if a client generated id is valid. + * + * @return true if client generated id is valid. + */ + @Override + protected boolean validateClientSuppliedId() { + return getIdValue() != null; + } +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PathSqlBuilderString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PathSqlBuilderString.java deleted file mode 100644 index e1c1ff46e..000000000 --- a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PathSqlBuilderString.java +++ /dev/null @@ -1,664 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.stringid; - -import com.querydsl.core.Tuple; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.StringPath; -import com.querydsl.sql.RelationalPathBase; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyArrayIndex; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PathSqlBuilder; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; -import de.fraunhofer.iosb.ilt.sta.query.OrderBy; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.settings.PersistenceSettings; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - */ -public class PathSqlBuilderString implements PathSqlBuilder { - - public static class TableRefString implements TableRef { - - public EntityType type; - public RelationalPathBase qPath; - public StringPath idPath; - - public TableRefString() { - } - - public TableRefString(TableRefString source) { - type = source.type; - qPath = source.qPath; - idPath = source.idPath; - } - - @Override - public EntityType getType() { - return type; - } - - @Override - public RelationalPathBase getqPath() { - return qPath; - } - - @Override - public void clear() { - type = null; - qPath = null; - idPath = null; - } - - @Override - public TableRef copy() { - TableRefString copy = new TableRefString(this); - return copy; - } - - @Override - public boolean isEmpty() { - return type == null && qPath == null; - } - } - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PathSqlBuilderString.class); - /** - * The prefix used for table aliases. The main entity is always - * <PREFIX>1. - */ - public static final String ALIAS_PREFIX = "e"; - private SQLQueryFactory queryFactory; - private SQLQuery sqlQuery; - private Set selectedProperties; - private final TableRefString lastPath = new TableRefString(); - private TableRefString mainTable; - private int aliasNr = 0; - private boolean isFilter = false; - private boolean needsDistinct = false; - - @Override - public synchronized SQLQuery buildFor(ResourcePath path, Query query, SQLQueryFactory sqlQueryFactory, PersistenceSettings settings) { - this.queryFactory = sqlQueryFactory; - selectedProperties = new HashSet<>(); - sqlQuery = queryFactory.select(new Expression[]{}); - lastPath.clear(); - aliasNr = 0; - List elements = new ArrayList<>(path.getPathElements()); - - int count = elements.size(); - for (int i = count - 1; i >= 0; i--) { - ResourcePathElement element = elements.get(i); - element.visit(this); - } - - if (query != null) { - boolean distict = false; - PgExpressionHandler handler = new PgExpressionHandler(this, mainTable.copy()); - for (OrderBy ob : query.getOrderBy()) { - handler.addOrderbyToQuery(ob, sqlQuery); - } - if (needsDistinct) { - sqlQuery.distinct(); - distict = true; - } - isFilter = true; - needsDistinct = false; - de.fraunhofer.iosb.ilt.sta.query.expression.Expression filter = query.getFilter(); - if (filter != null) { - handler.addFilterToQuery(filter, sqlQuery); - } - if (settings.getAlwaysOrderbyId()) { - sqlQuery.orderBy(mainTable.idPath.asc()); - } - if (needsDistinct && !distict) { - sqlQuery.distinct(); - } - } - - return sqlQuery; - } - - @Override - public void visit(EntityPathElement element) { - queryEntityType(element.getEntityType(), element.getId(), lastPath); - } - - @Override - public void visit(EntitySetPathElement element) { - queryEntityType(element.getEntityType(), null, lastPath); - } - - @Override - public void visit(PropertyPathElement element) { - selectedProperties.add(element.getProperty()); - selectedProperties.add(EntityProperty.Id); - } - - @Override - public void visit(CustomPropertyPathElement element) { - // noting to do for custom properties. - } - - @Override - public void visit(CustomPropertyArrayIndex element) { - // noting to do for custom properties. - } - - @Override - public void queryEntityType(EntityType type, Id targetId, TableRef lastRef) { - if (!(lastRef instanceof TableRefString)) { - throw new IllegalArgumentException("This implementation expect a TableRefString"); - } - TableRefString last = (TableRefString) lastRef; - - String id = null; - if (targetId != null) { - if (targetId.getBasicPersistenceType() != BasicPersistenceType.String) { - throw new IllegalArgumentException("This implementation expects String ids, not " + targetId.getBasicPersistenceType()); - } - id = (String) targetId.asBasicPersistenceType(); - } - switch (type) { - case Datastream: - queryDatastreams(id, last); - break; - - case MultiDatastream: - queryMultiDatastreams(id, last); - break; - - case FeatureOfInterest: - queryFeatures(id, last); - break; - - case HistoricalLocation: - queryHistLocations(id, last); - break; - - case Location: - queryLocations(id, last); - break; - - case Observation: - queryObservations(id, last); - break; - - case ObservedProperty: - queryObsProperties(id, last); - break; - - case Sensor: - querySensors(id, last); - break; - - case Thing: - queryThings(id, last); - break; - - default: - LOGGER.error("Unknown entity type {}!?", type); - throw new IllegalStateException("Unknown entity type " + type); - } - if (mainTable == null && !last.isEmpty()) { - mainTable = new TableRefString(last); - } - - } - - @Override - public Map> expressionsForProperty(EntityProperty property, Path qPath, Map> target) { - return PropertyResolver.expressionsForProperty(property, qPath, target); - } - - private void queryDatastreams(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QDatastreams qDataStreams = new QDatastreams(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qDataStreams, selectedProperties)); - sqlQuery.from(qDataStreams); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.thingId.eq(qThings.id)); - needsDistinct = true; - break; - - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.id.eq(qObservations.datastreamId)); - break; - - case Sensor: - QSensors qSensors = (QSensors) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.sensorId.eq(qSensors.id)); - needsDistinct = true; - break; - - case ObservedProperty: - QObsProperties qObsProperties = (QObsProperties) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.obsPropertyId.eq(qObsProperties.id)); - needsDistinct = true; - break; - - case Datastream: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Datastreams.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Datastream; - last.qPath = qDataStreams; - last.idPath = qDataStreams.id; - } - if (entityId != null) { - sqlQuery.where(qDataStreams.id.eq(entityId)); - } - } - - private void queryMultiDatastreams(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QMultiDatastreams qMultiDataStreams = new QMultiDatastreams(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qMultiDataStreams, selectedProperties)); - sqlQuery.from(qMultiDataStreams); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.thingId.eq(qThings.id)); - needsDistinct = true; - break; - - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.id.eq(qObservations.multiDatastreamId)); - break; - - case Sensor: - QSensors qSensors = (QSensors) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.sensorId.eq(qSensors.id)); - needsDistinct = true; - break; - - case ObservedProperty: - QObsProperties qObsProperties = (QObsProperties) last.qPath; - QMultiDatastreamsObsProperties qMdOp = new QMultiDatastreamsObsProperties(alias + "j1"); - sqlQuery.innerJoin(qMdOp).on(qObsProperties.id.eq(qMdOp.obsPropertyId)); - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.id.eq(qMdOp.multiDatastreamId)); - if (!isFilter) { - sqlQuery.orderBy(qMdOp.rank.asc()); - } else { - needsDistinct = true; - } - break; - - case MultiDatastream: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Datastreams.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.MultiDatastream; - last.qPath = qMultiDataStreams; - last.idPath = qMultiDataStreams.id; - } - if (entityId != null) { - sqlQuery.where(qMultiDataStreams.id.eq(entityId)); - } - } - - private void queryThings(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QThings qThings = new QThings(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qThings, selectedProperties)); - sqlQuery.from(qThings); - } else { - switch (last.type) { - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qDatastreams.thingId)); - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qMultiDatastreams.thingId)); - break; - - case HistoricalLocation: - QHistLocations qHistLocations = (QHistLocations) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qHistLocations.thingId)); - break; - - case Location: - QLocations qLocations = (QLocations) last.qPath; - QThingsLocations qTL = new QThingsLocations(alias + "j1"); - sqlQuery.innerJoin(qTL).on(qLocations.id.eq(qTL.locationId)); - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qTL.thingId)); - needsDistinct = true; - break; - - case Thing: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Things.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Thing; - last.qPath = qThings; - last.idPath = qThings.id; - } - if (entityId != null) { - sqlQuery.where(qThings.id.eq(entityId)); - } - } - - private void queryFeatures(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QFeatures qFeatures = new QFeatures(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qFeatures, selectedProperties)); - sqlQuery.from(qFeatures); - } else { - switch (last.type) { - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qFeatures).on(qFeatures.id.eq(qObservations.featureId)); - break; - - case FeatureOfInterest: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Features.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.FeatureOfInterest; - last.qPath = qFeatures; - last.idPath = qFeatures.id; - } - if (entityId != null) { - sqlQuery.where(qFeatures.id.eq(entityId)); - } - } - - private void queryHistLocations(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QHistLocations qHistLocations = new QHistLocations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qHistLocations, selectedProperties)); - sqlQuery.from(qHistLocations); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qHistLocations).on(qThings.id.eq(qHistLocations.thingId)); - needsDistinct = true; - break; - - case Location: - QLocations qLocations = (QLocations) last.qPath; - QLocationsHistLocations qLHL = new QLocationsHistLocations(alias + "j1"); - sqlQuery.innerJoin(qLHL).on(qLocations.id.eq(qLHL.locationId)); - sqlQuery.innerJoin(qHistLocations).on(qHistLocations.id.eq(qLHL.histLocationId)); - needsDistinct = true; - break; - - case HistoricalLocation: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto HistLocations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.HistoricalLocation; - last.qPath = qHistLocations; - last.idPath = qHistLocations.id; - } - if (entityId != null) { - sqlQuery.where(qHistLocations.id.eq(entityId)); - } - } - - private void queryLocations(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QLocations qLocations = new QLocations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qLocations, selectedProperties)); - sqlQuery.from(qLocations); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - QThingsLocations qTL = new QThingsLocations(alias + "j1"); - sqlQuery.innerJoin(qTL).on(qThings.id.eq(qTL.thingId)); - sqlQuery.innerJoin(qLocations).on(qLocations.id.eq(qTL.locationId)); - needsDistinct = true; - break; - - case HistoricalLocation: - QHistLocations qHistLocations = (QHistLocations) last.qPath; - QLocationsHistLocations qLHL = new QLocationsHistLocations(alias + "j1"); - sqlQuery.innerJoin(qLHL).on(qHistLocations.id.eq(qLHL.histLocationId)); - sqlQuery.innerJoin(qLocations).on(qLocations.id.eq(qLHL.locationId)); - needsDistinct = true; - break; - - case Location: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Locations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Location; - last.qPath = qLocations; - last.idPath = qLocations.id; - } - if (entityId != null) { - sqlQuery.where(qLocations.id.eq(entityId)); - } - } - - private void querySensors(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QSensors qSensors = new QSensors(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qSensors, selectedProperties)); - sqlQuery.from(qSensors); - } else { - switch (last.type) { - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qSensors).on(qSensors.id.eq(qDatastreams.sensorId)); - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qSensors).on(qSensors.id.eq(qMultiDatastreams.sensorId)); - break; - - case Sensor: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Sensors.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Sensor; - last.qPath = qSensors; - last.idPath = qSensors.id; - } - if (entityId != null) { - sqlQuery.where(qSensors.id.eq(entityId)); - } - } - - private void queryObservations(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QObservations qObservations = new QObservations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qObservations, selectedProperties)); - sqlQuery.from(qObservations); - } else { - switch (last.type) { - case FeatureOfInterest: - QFeatures qFeatures = (QFeatures) last.qPath; - sqlQuery.innerJoin(qObservations).on(qFeatures.id.eq(qObservations.featureId)); - needsDistinct = true; - break; - - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qObservations).on(qDatastreams.id.eq(qObservations.datastreamId)); - needsDistinct = true; - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qObservations).on(qMultiDatastreams.id.eq(qObservations.multiDatastreamId)); - needsDistinct = true; - break; - - case Observation: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Observations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Observation; - last.qPath = qObservations; - last.idPath = qObservations.id; - } - if (entityId != null) { - sqlQuery.where(qObservations.id.eq(entityId)); - } - } - - private void queryObsProperties(String entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QObsProperties qObsProperties = new QObsProperties(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qObsProperties, selectedProperties)); - sqlQuery.from(qObsProperties); - } else { - switch (last.type) { - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - QMultiDatastreamsObsProperties qMdOp = new QMultiDatastreamsObsProperties(alias + "j1"); - sqlQuery.innerJoin(qMdOp).on(qMultiDatastreams.id.eq(qMdOp.multiDatastreamId)); - sqlQuery.innerJoin(qObsProperties).on(qObsProperties.id.eq(qMdOp.obsPropertyId)); - needsDistinct = true; - needsDistinct = true; - break; - - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qObsProperties).on(qObsProperties.id.eq(qDatastreams.obsPropertyId)); - break; - case ObservedProperty: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto ObsProperties.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.ObservedProperty; - last.qPath = qObsProperties; - last.idPath = qObsProperties.id; - } - if (entityId != null) { - sqlQuery.where(qObsProperties.id.eq(entityId)); - } - } - -} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PostgresPersistenceManagerString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PostgresPersistenceManagerString.java index d2d0cd524..e47834aeb 100644 --- a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PostgresPersistenceManagerString.java +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PostgresPersistenceManagerString.java @@ -13,505 +13,99 @@ * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * aString with this program. If not, see . + * along with this program. If not, see . */ package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid; -import com.querydsl.core.Tuple; -import com.querydsl.sql.SQLExpressions; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import com.querydsl.sql.SQLTemplates; -import com.querydsl.sql.dml.SQLDeleteClause; -import com.querydsl.sql.spatial.PostGISTemplates; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; +import com.querydsl.core.types.dsl.StringPath; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.persistence.AbstractPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManagerString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.IdGenerationHandler; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; -import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PropertyResolver; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QDatastreamsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QFeaturesString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QHistLocationsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QLocationsHistLocationsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QLocationsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QMultiDatastreamsObsPropertiesString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QMultiDatastreamsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QObsPropertiesString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QObservationsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QSensorsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QThingsLocationsString; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths.QThingsString; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; -import de.fraunhofer.iosb.ilt.sta.settings.Settings; -import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; -import java.io.StringWriter; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.List; -import java.util.Map; -import javax.inject.Provider; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.sql.DataSource; -import liquibase.Contexts; -import liquibase.Liquibase; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.database.jvm.JdbcConnection; -import liquibase.exception.DatabaseException; -import liquibase.exception.LiquibaseException; -import liquibase.resource.ClassLoaderResourceAccessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * * @author jab + * @author scf */ -public class PostgresPersistenceManagerString extends AbstractPersistenceManager implements PostgresPersistenceManager { +public class PostgresPersistenceManagerString extends PostgresPersistenceManager { private static final String LIQUIBASE_CHANGELOG_FILENAME = "liquibase/tablesString.xml"; - private static final Logger LOGGER = LoggerFactory.getLogger(PostgresPersistenceManagerString.class); - private static class MyConnectionWrapper implements Provider { - - private final CoreSettings settings; - private Connection connection; - - public MyConnectionWrapper(CoreSettings settings) { - this.settings = settings; - } - - @Override - public Connection get() { - if (connection == null) { - try { - connection = getConnection(settings); - } catch (NamingException | SQLException ex) { - LOGGER.error("Could not inizialize " + getClass().getName(), ex); - } - } - return connection; - } - - public void clear() { - connection = null; - } - - } - - private MyConnectionWrapper connectionProvider; - private SQLQueryFactory queryFactory; - private CoreSettings settings; - - public PostgresPersistenceManagerString() { - } + private static final IdManagerString ID_MANAGER = new IdManagerString(); + private static EntityFactories entityFactories; + private static PropertyResolver propertyResolver; @Override public IdManager getIdManager() { - return IdManager.ID_MANAGER_STRING; + return ID_MANAGER; } @Override public void init(CoreSettings settings) { - this.settings = settings; - connectionProvider = new MyConnectionWrapper(settings); - } - - @Override - public CoreSettings getCoreSettings() { - return settings; - } - - @Override - public boolean doInsert(Entity entity) throws NoSuchEntityException, IncompleteEntityException { - EntityInserter ei = new EntityInserter(this); - switch (entity.getEntityType()) { - case Datastream: - ei.insertDatastream((Datastream) entity); - break; - - case MultiDatastream: - ei.insertMultiDatastream((MultiDatastream) entity); - break; - - case FeatureOfInterest: - ei.insertFeatureOfInterest((FeatureOfInterest) entity); - break; - - case HistoricalLocation: - ei.insertHistoricalLocation((HistoricalLocation) entity); - break; - - case Location: - ei.insertLocation((Location) entity); - break; - - case Observation: - ei.insertObservation((Observation) entity); - break; - - case ObservedProperty: - ei.insertObservedProperty((ObservedProperty) entity); - break; - - case Sensor: - ei.insertSensor((Sensor) entity); - break; - - case Thing: - ei.insertThing((Thing) entity); - break; - - default: - throw new IllegalStateException("Unknown entity type: " + entity.getEntityType().name()); - - } - return true; - } - - @Override - public boolean doDelete(EntityPathElement pathElement) throws NoSuchEntityException { - SQLQueryFactory qf = createQueryFactory(); - String id = (String) pathElement.getId().getValue(); - SQLDeleteClause delete; - EntityType type = pathElement.getEntityType(); - switch (type) { - case Datastream: - delete = qf.delete(QDatastreams.datastreams).where(QDatastreams.datastreams.id.eq(id)); - break; - - case MultiDatastream: - delete = qf.delete(QMultiDatastreams.multiDatastreams).where(QMultiDatastreams.multiDatastreams.id.eq(id)); - break; - - case FeatureOfInterest: - delete = qf.delete(QFeatures.features).where(QFeatures.features.id.eq(id)); - break; - - case HistoricalLocation: - delete = qf.delete(QHistLocations.histLocations).where(QHistLocations.histLocations.id.eq(id)); - break; - - case Location: { - delete = qf.delete(QLocations.locations).where(QLocations.locations.id.eq(id)); - long count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} Locations", count); - - // Also delete all historicalLocations that no longer reference any location - QHistLocations qhl = QHistLocations.histLocations; - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - delete = qf.delete(qhl) - .where(qhl.id.in( - SQLExpressions.select(qhl.id) - .from(qhl) - .leftJoin(qlhl).on(qhl.id.eq(qlhl.histLocationId)) - .where(qlhl.locationId.isNull()) - )); - count = delete.execute(); - LOGGER.debug("Deleted {} HistoricalLocations", count); - return true; - } - - case Observation: - delete = qf.delete(QObservations.observations).where(QObservations.observations.id.eq(id)); - break; - - case ObservedProperty: { - // First delete all MultiDatastreams that link to this ObservedProperty. - QMultiDatastreams qMd = QMultiDatastreams.multiDatastreams; - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - delete = qf.delete(qMd).where(qMd.id.in( - SQLExpressions.select(qMdOp.multiDatastreamId).from(qMdOp).where(qMdOp.obsPropertyId.eq(id)) - )); - long count = delete.execute(); - LOGGER.debug("Deleted {} MultiDatastreams.", count); - - delete = qf.delete(QObsProperties.obsProperties).where(QObsProperties.obsProperties.id.eq(id)); - count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} ObservedProperties", count); - return true; - } - - case Sensor: - delete = qf.delete(QSensors.sensors).where(QSensors.sensors.id.eq(id)); - break; - - case Thing: - delete = qf.delete(QThings.things).where(QThings.things.id.eq(id)); - break; - - default: - throw new NoSuchEntityException("Unknown entity type: " + pathElement.getEntityType()); - } - if (delete != null) { - long count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} entries of type {}", count, type); - } - return true; - } - - @Override - public boolean doUpdate(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException { - EntityInserter ei = new EntityInserter(this); - entity.setId(pathElement.getId()); - String id = (String) pathElement.getId().getValue(); - if (!ei.entityExists(entity)) { - throw new NoSuchEntityException("No entity of type " + pathElement.getEntityType() + " with id " + id); - } - EntityType type = pathElement.getEntityType(); - switch (type) { - case Datastream: - ei.updateDatastream((Datastream) entity, id); - break; - - case MultiDatastream: - ei.updateMultiDatastream((MultiDatastream) entity, id); - break; - - case FeatureOfInterest: - ei.updateFeatureOfInterest((FeatureOfInterest) entity, id); - break; - - case HistoricalLocation: - ei.updateHistoricalLocation((HistoricalLocation) entity, id); - break; - - case Location: - ei.updateLocation((Location) entity, id); - break; - - case Observation: - ei.updateObservation((Observation) entity, id); - break; - - case ObservedProperty: - ei.updateObservedProperty((ObservedProperty) entity, id); - break; - - case Sensor: - ei.updateSensor((Sensor) entity, id); - break; - - case Thing: - ei.updateThing((Thing) entity, id); - break; - - default: - throw new AssertionError(type.name()); - + super.init(settings); + IdGenerationHandlerString.setIdGenerationMode(settings.getPersistenceSettings().getIdGenerationMode()); + if (entityFactories == null) { + QCollection qCollection = new QCollection( + QDatastreamsString.DATASTREAMS, + QFeaturesString.FEATURES, + QHistLocationsString.HISTLOCATIONS, + QLocationsString.LOCATIONS, + QMultiDatastreamsString.MULTIDATASTREAMS, + QObsPropertiesString.OBSPROPERTIES, + QObservationsString.OBSERVATIONS, + QSensorsString.SENSORS, + QThingsString.THINGS, + QLocationsHistLocationsString.LOCATIONSHISTLOCATIONS, + QMultiDatastreamsObsPropertiesString.MULTIDATASTREAMSOBSPROPERTIES, + QThingsLocationsString.THINGSLOCATIONS); + init(qCollection); } - - return true; } - @Override - protected boolean doCommit() { - try { - if (!connectionProvider.get().isClosed()) { - connectionProvider.get().commit(); - return true; - } - } catch (SQLException ex) { - LOGGER.error("Exception rolling back.", ex); + private static synchronized void init(QCollection qCollection) { + if (entityFactories == null) { + entityFactories = new EntityFactories(ID_MANAGER, qCollection); + propertyResolver = new PropertyResolver<>(entityFactories, BasicPersistenceType.STRING); } - return false; } @Override - protected boolean doRollback() { - try { - if (!connectionProvider.get().isClosed()) { - LOGGER.info("Rolling back changes."); - connectionProvider.get().rollback(); - return true; - } - } catch (SQLException ex) { - LOGGER.error("Exception rolling back.", ex); - } - return false; + public PropertyResolver getPropertyResolver() { + return propertyResolver; } @Override - protected boolean doClose() { - try { - connectionProvider.get().close(); - return true; - } catch (SQLException ex) { - LOGGER.error("Exception closing.", ex); - } finally { - connectionProvider.clear(); - } - return false; - } - - public static Connection getConnection(CoreSettings settings) throws NamingException, SQLException { - Settings customSettings = settings.getPersistenceSettings().getCustomSettings(); - if (customSettings.contains(TAG_DATA_SOURCE)) { - String dataSourceName = customSettings.getString(TAG_DATA_SOURCE); - if (dataSourceName != null && !dataSourceName.isEmpty()) { - InitialContext cxt = new InitialContext(); - if (cxt == null) { - throw new IllegalStateException("No context!"); - } - - DataSource ds = (DataSource) cxt.lookup("java:/comp/env/" + dataSourceName); - if (ds == null) { - throw new IllegalStateException("Data source not found!"); - } - Connection connection = ds.getConnection(); - connection.setAutoCommit(false); - return connection; - } - } - if (!customSettings.contains(TAG_DB_DRIVER) || customSettings.getString(TAG_DB_DRIVER).isEmpty()) { - throw new IllegalArgumentException("Property '" + TAG_DB_DRIVER + "' must be non-empty"); - } - try { - Class.forName(customSettings.getString(TAG_DB_DRIVER)); - } catch (ClassNotFoundException ex) { - LOGGER.error("Could not initialise database.", ex); - throw new IllegalArgumentException(ex); - } - - Connection connection = DriverManager.getConnection( - customSettings.getString(TAG_DB_URL), - customSettings.getString(TAG_DB_USERNAME), - customSettings.getString(TAG_DB_PASSWORD)); - - connection.setAutoCommit(false); - return connection; - } - - public SQLQueryFactory createQueryFactory() { - if (queryFactory == null) { - SQLTemplates templates = PostGISTemplates.builder().quote().build(); - queryFactory = new SQLQueryFactory(templates, connectionProvider); - } - return queryFactory; + public String getLiquibaseChangelogFilename() { + return LIQUIBASE_CHANGELOG_FILENAME; } @Override - public boolean validatePath(ResourcePath path) { - ResourcePathElement element = path.getIdentifiedElement(); - if (element == null) { - return true; - } - ResourcePath tempPath = new ResourcePath(); - List elements = tempPath.getPathElements(); - while (element != null) { - elements.add(0, element); - element = element.getParent(); - } - return new EntityInserter(this).entityExists(tempPath); - } - - @Override - public Object get(ResourcePath path, Query query) { - ResourcePathElement lastElement = path.getLastElement(); - if (!(lastElement instanceof EntityPathElement) && !(lastElement instanceof EntitySetPathElement)) { - if (!query.getExpand().isEmpty()) { - LOGGER.warn("Expand only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); - query.getExpand().clear(); - } - if (!query.getSelect().isEmpty()) { - LOGGER.warn("Select only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); - query.getSelect().clear(); - } - } - - SQLQueryFactory qf = createQueryFactory(); - PathSqlBuilderString psb = new PathSqlBuilderString(); - SQLQuery sqlQuery = psb.buildFor(path, query, qf, settings.getPersistenceSettings()); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Generated SQL:\n{}", sqlQuery.getSQL().getSQL()); - } - - EntityCreator entityCreator = new EntityCreator(this, path, query, sqlQuery); - lastElement.visit(entityCreator); - Object entity = entityCreator.getEntity(); - - if (path.isValue() && entity instanceof Map) { - Map map = (Map) entity; - entity = map.get(entityCreator.getEntityName()); - } - - return entity; - } - - public long count(ResourcePath path, Query query) { - SQLQueryFactory qf = createQueryFactory(); - PathSqlBuilderString psb = new PathSqlBuilderString(); - SQLQuery sqlQuery = psb.buildFor(path, query, qf, settings.getPersistenceSettings()); - return sqlQuery.fetchCount(); - } - - @Override - public String checkForUpgrades() { - StringWriter out = new StringWriter(); - try { - Connection connection = getConnection(settings); - - Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); - Liquibase liquibase = new liquibase.Liquibase(LIQUIBASE_CHANGELOG_FILENAME, new ClassLoaderResourceAccessor(), database); - liquibase.update(new Contexts(), out); - database.commit(); - database.close(); - connection.close(); - - } catch (SQLException | DatabaseException | NamingException ex) { - LOGGER.error("Could not initialise database.", ex); - out.append("Failed to initialise database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } catch (LiquibaseException ex) { - LOGGER.error("Could not upgrade database.", ex); - out.append("Failed to upgrade database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } - return out.toString(); + public EntityFactories getEntityFactories() { + return entityFactories; } @Override - public String doUpgrades() { - StringWriter out = new StringWriter(); - try { - Connection connection = getConnection(settings); - - Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); - Liquibase liquibase = new liquibase.Liquibase(LIQUIBASE_CHANGELOG_FILENAME, new ClassLoaderResourceAccessor(), database); - liquibase.update(new Contexts()); - database.commit(); - database.close(); - connection.close(); - - } catch (SQLException | DatabaseException | NamingException ex) { - LOGGER.error("Could not initialise database.", ex); - out.append("Failed to initialise database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } catch (LiquibaseException ex) { - LOGGER.error("Could not upgrade database.", ex); - out.append("Failed to upgrade database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } - return out.toString(); + public IdGenerationHandler createIdGenerationHanlder(Entity e) { + return new IdGenerationHandlerString(e); } } diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PropertyHelper.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PropertyHelper.java deleted file mode 100644 index 1b475b5e7..000000000 --- a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PropertyHelper.java +++ /dev/null @@ -1,831 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.stringid; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.mysema.commons.lang.CloseableIterator; -import com.querydsl.core.Tuple; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.StringPath; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.geojson.GeoJsonDeserializier; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.StringId; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.Property; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.ResultType; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.util.GeoHelper; -import java.io.IOException; -import java.math.BigDecimal; -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.geojson.Polygon; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author scf - */ -public class PropertyHelper { - - public static interface entityFromTupleFactory { - - /** - * Creates a T, reading the Tuple with a qObject using no alias. - * - * @param tuple The tuple to create the Entity from. - * @param query The query used to request the data. - * @param dataSize The counter for the data size. This counts only the - * variable-sided elements, such as Observation.result and - * Thing.properties. - * @return The Entity created from the Tuple. - */ - public T create(Tuple tuple, Query query, DataSize dataSize); - - /** - * Get the primary key of the table of the entity this factory - * - * @return The primary key of the table of the entity this factory - * creates, using no alias. - */ - public StringPath getPrimaryKey(); - - /** - * Get the EntityType of the Entities created by this factory. - * - * @return The EntityType of the Entities created by this factory. - */ - public EntityType getEntityType(); - - } - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PropertyHelper.class); - private static final TypeReference> TYPE_LIST_STRING = new TypeReference>() { - // Empty on purpose. - }; - private static final TypeReference> TYPE_LIST_UOM = new TypeReference>() { - // Empty on purpose. - }; - private static final Map, entityFromTupleFactory> FACTORY_PER_ENTITY = new HashMap<>(); - - public static Expression[] getExpressions(Path qPath, Set selectedProperties) { - List> exprList = new ArrayList<>(); - if (selectedProperties.isEmpty()) { - PropertyResolver.expressionsForClass(qPath, exprList); - } else { - for (EntityProperty property : selectedProperties) { - PropertyResolver.expressionsForProperty(property, qPath, exprList); - } - } - return exprList.toArray(new Expression[exprList.size()]); - } - - public static EntitySet createSetFromTuples(entityFromTupleFactory factory, CloseableIterator tuples, Query query, long maxDataSize) { - EntitySet entitySet = new EntitySetImpl<>(factory.getEntityType()); - int count = 0; - DataSize size = new DataSize(); - int top = query.getTopOrDefault(); - while (tuples.hasNext()) { - Tuple tuple = tuples.next(); - entitySet.add(factory.create(tuple, query, size)); - count++; - if (count >= top) { - return entitySet; - } - if (size.getDataSize() > maxDataSize) { - LOGGER.debug("Size limit reached: {} > {}.", size.getDataSize(), maxDataSize); - return entitySet; - } - } - return entitySet; - } - - public static class DatastreamFactory implements PropertyHelper.entityFromTupleFactory { - - public static final DatastreamFactory withDefaultAlias = new DatastreamFactory(new QDatastreams(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QDatastreams qInstance; - - public DatastreamFactory(QDatastreams qInstance) { - this.qInstance = qInstance; - } - - @Override - public Datastream create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Datastream entity = new Datastream(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - entity.setObservationType(tuple.get(qInstance.observationType)); - - String observedArea = tuple.get(qInstance.observedArea.asText()); - if (observedArea != null) { - try { - Polygon polygon = GeoHelper.parsePolygon(observedArea); - entity.setObservedArea(polygon); - } catch (IllegalArgumentException e) { - // It's not a polygon, probably a point or a line. - } - } - ObservedProperty op = observedProperyFromId(tuple.get(qInstance.obsPropertyId)); - entity.setObservedProperty(op); - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - if (pTimeStart != null && pTimeEnd != null) { - entity.setPhenomenonTime(intervalFromTimes(pTimeStart, pTimeEnd)); - } - - Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); - Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); - if (rTimeStart != null && rTimeEnd != null) { - entity.setResultTime(intervalFromTimes(rTimeStart, rTimeEnd)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - entity.setSensor(sensorFromId(tuple.get(qInstance.sensorId))); - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - - entity.setUnitOfMeasurement(new UnitOfMeasurement(tuple.get(qInstance.unitName), tuple.get(qInstance.unitSymbol), tuple.get(qInstance.unitDefinition))); - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Datastream; - } - - } - - public static class MultiDatastreamFactory implements PropertyHelper.entityFromTupleFactory { - - public static final MultiDatastreamFactory withDefaultAlias = new MultiDatastreamFactory(new QMultiDatastreams(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QMultiDatastreams qInstance; - - public MultiDatastreamFactory(QMultiDatastreams qInstance) { - this.qInstance = qInstance; - } - - @Override - public MultiDatastream create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - MultiDatastream entity = new MultiDatastream(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - List observationTypes = jsonToObject(tuple.get(qInstance.observationTypes), TYPE_LIST_STRING); - entity.setMultiObservationDataTypes(observationTypes); - - String observedArea = tuple.get(qInstance.observedArea.asText()); - if (observedArea != null) { - try { - Polygon polygon = GeoHelper.parsePolygon(observedArea); - entity.setObservedArea(polygon); - } catch (IllegalArgumentException e) { - // It's not a polygon, probably a point or a line. - } - } - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - if (pTimeStart != null && pTimeEnd != null) { - entity.setPhenomenonTime(intervalFromTimes(pTimeStart, pTimeEnd)); - } - - Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); - Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); - if (rTimeStart != null && rTimeEnd != null) { - entity.setResultTime(intervalFromTimes(rTimeStart, rTimeEnd)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - entity.setSensor(sensorFromId(tuple.get(qInstance.sensorId))); - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - - List units = jsonToObject(tuple.get(qInstance.unitOfMeasurements), TYPE_LIST_UOM); - entity.setUnitOfMeasurements(units); - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.MultiDatastream; - } - - } - - public static class ThingFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ThingFactory withDefaultAlias = new ThingFactory(new QThings(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QThings qInstance; - - public ThingFactory(QThings qInstance) { - this.qInstance = qInstance; - } - - @Override - public Thing create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Thing entity = new Thing(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - dataSize.increase(props == null ? 0 : props.length()); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Thing; - } - - } - - public static class FeatureOfInterestFactory implements PropertyHelper.entityFromTupleFactory { - - public static final FeatureOfInterestFactory withDefaultAlias = new FeatureOfInterestFactory(new QFeatures(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QFeatures qInstance; - - public FeatureOfInterestFactory(QFeatures qInstance) { - this.qInstance = qInstance; - } - - @Override - public FeatureOfInterest create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - FeatureOfInterest entity = new FeatureOfInterest(); - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String encodingType = tuple.get(qInstance.encodingType); - entity.setEncodingType(encodingType); - - if (select.isEmpty() || select.contains(EntityProperty.Feature)) { - String locationString = tuple.get(qInstance.feature); - dataSize.increase(locationString == null ? 0 : locationString.length()); - entity.setFeature(locationFromEncoding(encodingType, locationString)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.FeatureOfInterest; - } - - } - - public static class HistoricalLocationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final HistoricalLocationFactory withDefaultAlias = new HistoricalLocationFactory(new QHistLocations(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QHistLocations qInstance; - - public HistoricalLocationFactory(QHistLocations qInstance) { - this.qInstance = qInstance; - } - - @Override - public HistoricalLocation create(Tuple tuple, Query query, DataSize dataSize) { - HistoricalLocation entity = new HistoricalLocation(); - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - entity.setTime(instantFromTime(tuple.get(qInstance.time))); - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.HistoricalLocation; - } - - } - - public static class LocationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final LocationFactory withDefaultAlias = new LocationFactory(new QLocations(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QLocations qInstance; - - public LocationFactory(QLocations qInstance) { - this.qInstance = qInstance; - } - - @Override - public Location create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - Location entity = new Location(); - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String encodingType = tuple.get(qInstance.encodingType); - entity.setEncodingType(encodingType); - - if (select.isEmpty() || select.contains(EntityProperty.Location)) { - String locationString = tuple.get(qInstance.location); - dataSize.increase(locationString == null ? 0 : locationString.length()); - entity.setLocation(locationFromEncoding(encodingType, locationString)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Location; - } - - } - - public static class SensorFactory implements PropertyHelper.entityFromTupleFactory { - - public static final SensorFactory withDefaultAlias = new SensorFactory(new QSensors(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QSensors qInstance; - - public SensorFactory(QSensors qInstance) { - this.qInstance = qInstance; - } - - @Override - public Sensor create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Sensor entity = new Sensor(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - entity.setEncodingType(tuple.get(qInstance.encodingType)); - - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Metadata)) { - String metaDataString = tuple.get(qInstance.metadata); - dataSize.increase(metaDataString == null ? 0 : metaDataString.length()); - entity.setMetadata(metaDataString); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Sensor; - } - - } - - public static class ObservationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ObservationFactory withDefaultAlias = new ObservationFactory(new QObservations(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QObservations qInstance; - - public ObservationFactory(QObservations qInstance) { - this.qInstance = qInstance; - } - - @Override - public Observation create(Tuple tuple, Query query, DataSize dataSize) { - Observation entity = new Observation(); - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - String dsId = tuple.get(qInstance.datastreamId); - if (dsId != null) { - entity.setDatastream(datastreamFromId(dsId)); - } - String mDsId = tuple.get(qInstance.multiDatastreamId); - if (mDsId != null) { - entity.setMultiDatastream(multiDatastreamFromId(mDsId)); - } - - entity.setFeatureOfInterest(featureOfInterestFromId(tuple.get(qInstance.featureId))); - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Parameters)) { - String props = tuple.get(qInstance.parameters); - dataSize.increase(props == null ? 0 : props.length()); - entity.setParameters(jsonToObject(props, Map.class)); - } - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - entity.setPhenomenonTime(valueFromTimes(pTimeStart, pTimeEnd)); - - if (select.isEmpty() || select.contains(EntityProperty.Result)) { - Byte resultTypeOrd = tuple.get(qInstance.resultType); - if (resultTypeOrd != null) { - ResultType resultType = ResultType.fromSqlValue(resultTypeOrd); - switch (resultType) { - case BOOLEAN: - entity.setResult(tuple.get(qInstance.resultBoolean)); - break; - - case NUMBER: - try { - entity.setResult(new BigDecimal(tuple.get(qInstance.resultString))); - } catch (NumberFormatException e) { - // It was not a Number? Use the double value. - entity.setResult(tuple.get(qInstance.resultNumber)); - } - break; - - case OBJECT_ARRAY: - String jsonData = tuple.get(qInstance.resultJson); - dataSize.increase(jsonData == null ? 0 : jsonData.length()); - entity.setResult(jsonToTree(jsonData)); - break; - - case STRING: - String stringData = tuple.get(qInstance.resultString); - dataSize.increase(stringData == null ? 0 : stringData.length()); - entity.setResult(stringData); - break; - } - } - } - - if (select.isEmpty() || select.contains(EntityProperty.ResultQuality)) { - String resultQuality = tuple.get(qInstance.resultQuality); - dataSize.increase(resultQuality == null ? 0 : resultQuality.length()); - entity.setResultQuality(jsonToObject(resultQuality, Object.class)); - } - - entity.setResultTime(instantFromTime(tuple.get(qInstance.resultTime))); - - Timestamp vTimeStart = tuple.get(qInstance.validTimeStart); - Timestamp vTimeEnd = tuple.get(qInstance.validTimeEnd); - if (vTimeStart != null && vTimeEnd != null) { - entity.setValidTime(intervalFromTimes(vTimeStart, vTimeEnd)); - } - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Observation; - } - - } - - public static class ObservedPropertyFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ObservedPropertyFactory withDefaultAlias = new ObservedPropertyFactory(new QObsProperties(PathSqlBuilderString.ALIAS_PREFIX + "1")); - private final QObsProperties qInstance; - - public ObservedPropertyFactory(QObsProperties qInstance) { - this.qInstance = qInstance; - } - - @Override - public ObservedProperty create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - ObservedProperty entity = new ObservedProperty(); - entity.setDefinition(tuple.get(qInstance.definition)); - entity.setDescription(tuple.get(qInstance.description)); - String id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new StringId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public StringPath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.ObservedProperty; - } - - } - - static { - FACTORY_PER_ENTITY.put(Datastream.class, DatastreamFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(MultiDatastream.class, MultiDatastreamFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Thing.class, ThingFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(FeatureOfInterest.class, FeatureOfInterestFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(HistoricalLocation.class, HistoricalLocationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Location.class, LocationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Sensor.class, SensorFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Observation.class, ObservationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(ObservedProperty.class, ObservedPropertyFactory.withDefaultAlias); - } - - /** - * Get the factory for the given entity class, using the default alias - * PathSqlBuilderString.ALIAS_PREFIX + "1". - * - * @param The type of entity to get the factory for. - * @param clazz The class of the entity to get the factory for. - * @return the factory for the given entity class. - */ - public static entityFromTupleFactory getFactoryFor(Class clazz) { - entityFromTupleFactory factory = FACTORY_PER_ENTITY.get(clazz); - if (factory == null) { - throw new AssertionError("No factory found for " + clazz.getName()); - } - return (entityFromTupleFactory) factory; - } - - private static TimeInterval intervalFromTimes(Timestamp timeStart, Timestamp timeEnd) { - if (timeStart == null) { - timeStart = Timestamp.valueOf(LocalDateTime.MAX); - } - if (timeEnd == null) { - timeEnd = Timestamp.valueOf(LocalDateTime.MIN); - } - if (timeEnd.before(timeStart)) { - return null; - } else { - return TimeInterval.create(timeStart.getTime(), timeEnd.getTime()); - } - } - - private static TimeInstant instantFromTime(Timestamp time) { - if (time == null) { - return new TimeInstant(null); - } - return TimeInstant.create(time.getTime()); - } - - private static TimeValue valueFromTimes(Timestamp timeStart, Timestamp timeEnd) { - if (timeEnd == null || timeEnd.equals(timeStart)) { - return instantFromTime(timeStart); - } - return intervalFromTimes(timeStart, timeEnd); - } - - private static Datastream datastreamFromId(String id) { - if (id == null) { - return null; - } - Datastream ds = new Datastream(); - ds.setId(new StringId(id)); - ds.setExportObject(false); - return ds; - } - - private static MultiDatastream multiDatastreamFromId(String id) { - if (id == null) { - return null; - } - MultiDatastream ds = new MultiDatastream(); - ds.setId(new StringId(id)); - ds.setExportObject(false); - return ds; - } - - private static FeatureOfInterest featureOfInterestFromId(String id) { - if (id == null) { - return null; - } - FeatureOfInterest foi = new FeatureOfInterest(); - foi.setId(new StringId(id)); - foi.setExportObject(false); - return foi; - } - - private static ObservedProperty observedProperyFromId(String id) { - if (id == null) { - return null; - } - ObservedProperty op = new ObservedProperty(); - op.setId(new StringId(id)); - op.setExportObject(false); - return op; - } - - private static Sensor sensorFromId(String id) { - if (id == null) { - return null; - } - Sensor sensor = new Sensor(); - sensor.setId(new StringId(id)); - sensor.setExportObject(false); - return sensor; - } - - private static Thing thingFromId(String id) { - if (id == null) { - return null; - } - Thing thing = new Thing(); - thing.setId(new StringId(id)); - thing.setExportObject(false); - return thing; - } - - public static Object locationFromEncoding(String encodingType, String locationString) { - if (locationString == null || locationString.isEmpty()) { - return null; - } - if (encodingType != null && GeoJsonDeserializier.encodings.contains(encodingType.toLowerCase())) { - try { - Object geoJson = new GeoJsonDeserializier().deserialize(locationString); - return geoJson; - } catch (IOException ex) { - LOGGER.error("Failed to deserialise geoJson."); - - } - } else { - try { - Map map = jsonToObject(locationString, Map.class - ); - return map; - } catch (Exception e) { - LOGGER.trace("Not a map."); - } - return locationString; - } - return null; - } - - public static JsonNode jsonToTree(String json) { - if (json == null) { - return null; - } - - try { - return new ObjectMapper().readTree(json); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - - public static T jsonToObject(String json, Class clazz) { - if (json == null) { - return null; - } - try { - return new ObjectMapper().readValue(json, clazz); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - - public static T jsonToObject(String json, TypeReference typeReference) { - if (json == null) { - return null; - } - try { - return new ObjectMapper().readValue(json, typeReference); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - -} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PropertyResolver.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PropertyResolver.java deleted file mode 100644 index cad7d5585..000000000 --- a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/PropertyResolver.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.stringid; - -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; -import de.fraunhofer.iosb.ilt.sta.path.Property; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_END; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_START; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - */ -public class PropertyResolver { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PropertyResolver.class); - - private static interface ExpressionFactory { - - Expression get(T qPath); - } - - private static final Map> EP_MAP_SINGLE = new HashMap<>(); - private static final Map>> EP_MAP_MULTI = new HashMap<>(); - private static final Map> ALL_FOR_CLASS = new HashMap<>(); - - static { - addEntry(EntityProperty.Id, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.Name, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.name); - addEntry(EntityProperty.Description, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.description); - addEntry(EntityProperty.ObservationType, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.observationType); - addEntry(EntityProperty.ObservedArea, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.observedArea.asText()); - addEntry(EntityProperty.PhenomenonTime, QDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QDatastreams qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QDatastreams qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Properties, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.properties); - addEntry(EntityProperty.ResultTime, QDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QDatastreams qPath) -> qPath.resultTimeStart); - addEntry(EntityProperty.ResultTime, QDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QDatastreams qPath) -> qPath.resultTimeEnd); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "definition", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitDefinition); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "name", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitName); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "symbol", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitSymbol); - addEntry(NavigationProperty.Sensor, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.sensorId); - addEntry(NavigationProperty.ObservedProperty, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.obsPropertyId); - addEntry(NavigationProperty.Thing, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.Name, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.name); - addEntry(EntityProperty.Description, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.description); - addEntry(EntityProperty.MultiObservationDataTypes, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.observationTypes); - addEntry(EntityProperty.ObservedArea, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.observedArea.asText()); - addEntry(EntityProperty.PhenomenonTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Properties, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.properties); - addEntry(EntityProperty.ResultTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.resultTimeStart); - addEntry(EntityProperty.ResultTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.resultTimeEnd); - addEntry(EntityProperty.UnitOfMeasurements, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.unitOfMeasurements); - addEntry(NavigationProperty.Sensor, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.sensorId); - addEntry(NavigationProperty.Thing, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.id); - addEntry(EntityProperty.Name, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.name); - addEntry(EntityProperty.Description, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.encodingType); - addEntry(EntityProperty.Feature, QFeatures.class, "j", (ExpressionFactory) (QFeatures qPath) -> qPath.feature); - addEntry(EntityProperty.Feature, QFeatures.class, "g", (ExpressionFactory) (QFeatures qPath) -> qPath.geom); - addEntry(EntityProperty.Properties, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.id); - addEntry(EntityProperty.Time, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.time); - addEntry(NavigationProperty.Thing, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.id); - addEntry(EntityProperty.Name, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.name); - addEntry(EntityProperty.Description, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.encodingType); - addEntry(EntityProperty.Location, QLocations.class, "j", (ExpressionFactory) (QLocations qPath) -> qPath.location); - addEntry(EntityProperty.Location, QLocations.class, "g", (ExpressionFactory) (QLocations qPath) -> qPath.geom); - addEntry(EntityProperty.Properties, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.id); - addEntry(EntityProperty.Definition, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.definition); - addEntry(EntityProperty.Description, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.description); - addEntry(EntityProperty.Name, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.name); - addEntry(EntityProperty.Properties, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.id); - addEntry(EntityProperty.Parameters, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.parameters); - addEntry(EntityProperty.PhenomenonTime, QObservations.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QObservations qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QObservations.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QObservations qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Result, QObservations.class, "n", (ExpressionFactory) (QObservations qPath) -> qPath.resultNumber); - addEntry(EntityProperty.Result, QObservations.class, "b", (ExpressionFactory) (QObservations qPath) -> qPath.resultBoolean); - addEntry(EntityProperty.Result, QObservations.class, "s", (ExpressionFactory) (QObservations qPath) -> qPath.resultString); - addEntry(EntityProperty.Result, QObservations.class, "j", (ExpressionFactory) (QObservations qPath) -> qPath.resultJson); - addEntry(EntityProperty.Result, QObservations.class, "t", (ExpressionFactory) (QObservations qPath) -> qPath.resultType); - addEntry(EntityProperty.ResultQuality, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.resultQuality); - addEntry(EntityProperty.ResultTime, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.resultTime); - addEntry(EntityProperty.ValidTime, QObservations.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QObservations qPath) -> qPath.validTimeStart); - addEntry(EntityProperty.ValidTime, QObservations.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QObservations qPath) -> qPath.validTimeEnd); - addEntry(NavigationProperty.FeatureOfInterest, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.featureId); - addEntry(NavigationProperty.Datastream, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.datastreamId); - addEntry(NavigationProperty.MultiDatastream, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.multiDatastreamId); - - addEntry(EntityProperty.Id, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.id); - addEntry(EntityProperty.Name, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.name); - addEntry(EntityProperty.Description, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.encodingType); - addEntry(EntityProperty.Metadata, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.metadata); - addEntry(EntityProperty.Properties, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.id); - addEntry(EntityProperty.Name, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.name); - addEntry(EntityProperty.Description, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.description); - addEntry(EntityProperty.Properties, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.properties); - } - - /** - * - * @param qPath The path to get expressions for. - * @param target The list to add to. If null a new list will be created. - * @return The target list, or a new list if target was null. - */ - public static List> expressionsForClass(Path qPath, List> target) { - List list = ALL_FOR_CLASS.get(qPath.getClass()); - if (target == null) { - target = new ArrayList<>(); - } - for (ExpressionFactory f : list) { - target.add(f.get(qPath)); - } - return target; - } - - public static Expression expressionForProperty(EntityProperty property, Path qPath) { - Map innerMap = EP_MAP_SINGLE.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("ObservedProperty has no property called " + property.toString()); - } - return innerMap.get(qPath.getClass()).get(qPath); - } - - /** - * Get a list of expressions for the given property and path. Add it to the - * given list, or a new list. - * - * @param property The property to get expressions for. - * @param qPath The path to get expressions for. - * @param target The list to add to. If null a new list will be created. - * @return The target list, or a new list if target was null. - */ - public static List> expressionsForProperty(EntityProperty property, Path qPath, List< Expression> target) { - Map> innerMap = EP_MAP_MULTI.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("ObservedProperty has no property called " + property.toString()); - } - Map coreMap = innerMap.get(qPath.getClass()); - if (target == null) { - target = new ArrayList<>(); - } - for (Map.Entry es : coreMap.entrySet()) { - target.add(es.getValue().get(qPath)); - } - return target; - } - - /** - * Get a Map of expressions for the given property and path. Add it to the - * given Map, or a new Map. - * - * @param property The property to get expressions for. - * @param qPath The path to get expressions for. - * @param target The Map to add to. If null a new Map will be created. - * @return The target Map, or a new Map if target was null. - */ - public static Map> expressionsForProperty(EntityProperty property, Path qPath, Map> target) { - Map> innerMap = EP_MAP_MULTI.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("We do not know any property called " + property.toString()); - } - Map coreMap = innerMap.get(qPath.getClass()); - if (coreMap == null) { - throw new IllegalArgumentException("No property called " + property.toString() + " for " + qPath.getClass()); - } - if (target == null) { - target = new LinkedHashMap<>(); - } - for (Map.Entry es : coreMap.entrySet()) { - target.put(es.getKey(), es.getValue().get(qPath)); - } - return target; - } - - private static void addEntry(Property p, Class c, ExpressionFactory f) { - addEntrySingle(p, c, f); - addEntryMulti(p, c, null, f); - addToAll(c, f); - } - - private static void addEntry(Property p, Class c, String name, ExpressionFactory f) { - addEntrySingle(p, c, f); - addEntryMulti(p, c, name, f); - addToAll(c, f); - } - - private static void addToAll(Class c, ExpressionFactory f) { - List list = ALL_FOR_CLASS.get(c); - if (list == null) { - list = new ArrayList<>(); - ALL_FOR_CLASS.put(c, list); - } - list.add(f); - } - - private static void addEntrySingle(Property p, Class c, ExpressionFactory f) { - Map innerMap = EP_MAP_SINGLE.get(p); - if (innerMap == null) { - innerMap = new HashMap<>(); - EP_MAP_SINGLE.put(p, innerMap); - } - if (innerMap.containsKey(c)) { - LOGGER.trace("Class {} already has a registration for {}.", c.getName(), p); - return; - } - innerMap.put(c, f); - } - - private static void addEntryMulti(Property p, Class c, String name, ExpressionFactory f) { - Map> innerMap = EP_MAP_MULTI.get(p); - if (innerMap == null) { - innerMap = new HashMap<>(); - EP_MAP_MULTI.put(p, innerMap); - } - Map coreMap = innerMap.get(c); - if (coreMap == null) { - coreMap = new LinkedHashMap<>(); - innerMap.put(c, coreMap); - } - if (name == null) { - name = Integer.toString(coreMap.size()); - } - coreMap.put(name, f); - } -} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QDatastreamsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QDatastreamsString.java new file mode 100644 index 000000000..624bbc4a3 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QDatastreamsString.java @@ -0,0 +1,76 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import java.sql.Types; + +/** + * QDatastreamsString is a Querydsl query type for QDatastreamsString + */ +public class QDatastreamsString extends AbstractQDatastreams { + + private static final long serialVersionUID = -546602411; + private static final String TABLE_NAME = "DATASTREAMS"; + + public static final QDatastreamsString DATASTREAMS = new QDatastreamsString(TABLE_NAME); + + public final StringPath id = createString("id"); + + public final StringPath obsPropertyId = createString("obsPropertyId"); + + public final StringPath sensorId = createString("sensorId"); + + public final StringPath thingId = createString("thingId"); + + public QDatastreamsString(String variable) { + super(QDatastreamsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(obsPropertyId, ColumnMetadata.named("OBS_PROPERTY_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(sensorId, ColumnMetadata.named("SENSOR_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + /** + * @return the id + */ + @Override + public StringPath getId() { + return id; + } + + /** + * @return the obsPropertyId + */ + @Override + public StringPath getObsPropertyId() { + return obsPropertyId; + } + + /** + * @return the sensorId + */ + @Override + public StringPath getSensorId() { + return sensorId; + } + + /** + * @return the thingId + */ + @Override + public StringPath getThingId() { + return thingId; + } + + @Override + public QDatastreamsString newWithAlias(String variable) { + return new QDatastreamsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QFeaturesString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QFeaturesString.java new file mode 100644 index 000000000..098c5365f --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QFeaturesString.java @@ -0,0 +1,43 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQFeatures; +import java.sql.Types; + +/** + * QFeaturesString is a Querydsl query type for QFeaturesString + */ +public class QFeaturesString extends AbstractQFeatures { + + private static final long serialVersionUID = 1880834929; + private static final String TABLE_NAME = "FEATURES"; + + public static final QFeaturesString FEATURES = new QFeaturesString(TABLE_NAME); + + public final StringPath id = createString("id"); + + public QFeaturesString(String variable) { + super(QFeaturesString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + /** + * @return the id + */ + @Override + public StringPath getId() { + return id; + } + + @Override + public QFeaturesString newWithAlias(String variable) { + return new QFeaturesString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QHistLocationsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QHistLocationsString.java new file mode 100644 index 000000000..8295a4437 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QHistLocationsString.java @@ -0,0 +1,51 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import java.sql.Types; + +/** + * QHistLocationsString is a Querydsl query type for QHistLocationsString + */ +public class QHistLocationsString extends AbstractQHistLocations { + + private static final long serialVersionUID = 2040692648; + private static final String TABLE_NAME = "HIST_LOCATIONS"; + + public static final QHistLocationsString HISTLOCATIONS = new QHistLocationsString(TABLE_NAME); + + public final StringPath id = createString("id"); + + public final StringPath thingId = createString("thingId"); + + public QHistLocationsString(String variable) { + super(QHistLocationsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + /** + * @return the id + */ + @Override + public StringPath getId() { + return id; + } + + @Override + public StringPath getThingId() { + return thingId; + } + + @Override + public QHistLocationsString newWithAlias(String variable) { + return new QHistLocationsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QLocationsHistLocationsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QLocationsHistLocationsString.java new file mode 100644 index 000000000..fc5d4a95c --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QLocationsHistLocationsString.java @@ -0,0 +1,49 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocationsHistLocations; +import java.sql.Types; + +/** + * QLocationsHistLocationsString is a Querydsl query type for + * QLocationsHistLocationsString + */ +public class QLocationsHistLocationsString extends AbstractQLocationsHistLocations { + + private static final long serialVersionUID = -678464558; + private static final String TABLE_NAME = "LOCATIONS_HIST_LOCATIONS"; + + public static final QLocationsHistLocationsString LOCATIONSHISTLOCATIONS = new QLocationsHistLocationsString(TABLE_NAME); + + public final StringPath histLocationId = createString("histLocationId"); + + public final StringPath locationId = createString("locationId"); + + public QLocationsHistLocationsString(String variable) { + super(QLocationsHistLocationsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(histLocationId, ColumnMetadata.named("HIST_LOCATION_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(locationId, ColumnMetadata.named("LOCATION_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + @Override + public StringPath getLocationId() { + return locationId; + } + + @Override + public StringPath getHistLocationId() { + return histLocationId; + } + + @Override + public QLocationsHistLocationsString newWithAlias(String variable) { + return new QLocationsHistLocationsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QLocationsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QLocationsString.java new file mode 100644 index 000000000..48f5e60d1 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QLocationsString.java @@ -0,0 +1,51 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocations; +import java.sql.Types; + +/** + * QLocationsString is a Querydsl query type for QLocationsString + */ +public class QLocationsString extends AbstractQLocations { + + private static final long serialVersionUID = 1694621354; + private static final String TABLE_NAME = "LOCATIONS"; + + public static final QLocationsString LOCATIONS = new QLocationsString(TABLE_NAME); + + public final StringPath genFoiId = createString("genFoiId"); + + public final StringPath id = createString("id"); + + public QLocationsString(String variable) { + super(QLocationsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(genFoiId, ColumnMetadata.named("GEN_FOI_ID").ofType(Types.VARCHAR).withSize(36)); + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + /** + * @return the id + */ + @Override + public StringPath getId() { + return id; + } + + @Override + public StringPath getGenFoiId() { + return genFoiId; + } + + @Override + public QLocationsString newWithAlias(String variable) { + return new QLocationsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QMultiDatastreamsObsPropertiesString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QMultiDatastreamsObsPropertiesString.java new file mode 100644 index 000000000..fb2015b4e --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QMultiDatastreamsObsPropertiesString.java @@ -0,0 +1,49 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreamsObsProperties; +import java.sql.Types; + +/** + * QMultiDatastreamsObsPropertiesString is a Querydsl query type for + * QMultiDatastreamsObsPropertiesString + */ +public class QMultiDatastreamsObsPropertiesString extends AbstractQMultiDatastreamsObsProperties { + + private static final long serialVersionUID = 1753892463; + private static final String TABLE_NAME = "MULTI_DATASTREAMS_OBS_PROPERTIES"; + + public static final QMultiDatastreamsObsPropertiesString MULTIDATASTREAMSOBSPROPERTIES = new QMultiDatastreamsObsPropertiesString(TABLE_NAME); + + public final StringPath multiDatastreamId = createString("multiDatastreamId"); + + public final StringPath obsPropertyId = createString("obsPropertyId"); + + public QMultiDatastreamsObsPropertiesString(String variable) { + super(QMultiDatastreamsObsPropertiesString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(multiDatastreamId, ColumnMetadata.named("MULTI_DATASTREAM_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(obsPropertyId, ColumnMetadata.named("OBS_PROPERTY_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + @Override + public StringPath getMultiDatastreamId() { + return multiDatastreamId; + } + + @Override + public StringPath getObsPropertyId() { + return obsPropertyId; + } + + @Override + public QMultiDatastreamsObsPropertiesString newWithAlias(String variable) { + return new QMultiDatastreamsObsPropertiesString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QMultiDatastreamsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QMultiDatastreamsString.java new file mode 100644 index 000000000..3de589360 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QMultiDatastreamsString.java @@ -0,0 +1,59 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import java.sql.Types; + +/** + * QMultiDatastreamsString is a Querydsl query type for QMultiDatastreamsString + */ +public class QMultiDatastreamsString extends AbstractQMultiDatastreams { + + private static final long serialVersionUID = -1888350652; + private static final String TABLE_NAME = "MULTI_DATASTREAMS"; + + public static final QMultiDatastreamsString MULTIDATASTREAMS = new QMultiDatastreamsString(TABLE_NAME); + + public final StringPath id = createString("id"); + + public final StringPath sensorId = createString("sensorId"); + + public final StringPath thingId = createString("thingId"); + + public QMultiDatastreamsString(String variable) { + super(QMultiDatastreamsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + public void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(sensorId, ColumnMetadata.named("SENSOR_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + /** + * @return the id + */ + @Override + public StringPath getId() { + return id; + } + + @Override + public StringPath getThingId() { + return thingId; + } + + @Override + public StringPath getSensorId() { + return sensorId; + } + + @Override + public QMultiDatastreamsString newWithAlias(String variable) { + return new QMultiDatastreamsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QObsPropertiesString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QObsPropertiesString.java new file mode 100644 index 000000000..b315b7672 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QObsPropertiesString.java @@ -0,0 +1,44 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObsProperties; +import java.sql.Types; + +/** + * QObsPropertiesString is a Querydsl query type for QObsPropertiesString + */ +public class QObsPropertiesString extends AbstractQObsProperties { + + private static final long serialVersionUID = 664655775; + private static final String TABLE_NAME = "OBS_PROPERTIES"; + + public static final QObsPropertiesString OBSPROPERTIES = new QObsPropertiesString(TABLE_NAME); + + public final StringPath id = createString("id"); + + public QObsPropertiesString(String variable) { + super(QObsPropertiesString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + /** + * @return the id + */ + @Override + public StringPath getId() { + return id; + } + + @Override + public QObsPropertiesString newWithAlias(String variable) { + return new QObsPropertiesString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QObservationsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QObservationsString.java new file mode 100644 index 000000000..62317e02d --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QObservationsString.java @@ -0,0 +1,64 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import java.sql.Types; + +/** + * QObservationsString is a Querydsl query type for QObservationsString + */ +public class QObservationsString extends AbstractQObservations { + + private static final long serialVersionUID = 974377723; + private static final String TABLE_NAME = "OBSERVATIONS"; + + public static final QObservationsString OBSERVATIONS = new QObservationsString(TABLE_NAME); + + public final StringPath datastreamId = createString("datastreamId"); + + public final StringPath featureId = createString("featureId"); + + public final StringPath id = createString("id"); + + public final StringPath multiDatastreamId = createString("multiDatastreamId"); + + public QObservationsString(String variable) { + super(QObservationsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(datastreamId, ColumnMetadata.named("DATASTREAM_ID").ofType(Types.VARCHAR).withSize(36)); + addMetadata(featureId, ColumnMetadata.named("FEATURE_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(multiDatastreamId, ColumnMetadata.named("MULTI_DATASTREAM_ID").ofType(Types.VARCHAR).withSize(36)); + } + + @Override + public StringPath getId() { + return id; + } + + @Override + public StringPath getDatastreamId() { + return datastreamId; + } + + @Override + public StringPath getFeatureId() { + return featureId; + } + + @Override + public StringPath getMultiDatastreamId() { + return multiDatastreamId; + } + + @Override + public QObservationsString newWithAlias(String variable) { + return new QObservationsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QSensorsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QSensorsString.java new file mode 100644 index 000000000..25fee6ca0 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QSensorsString.java @@ -0,0 +1,40 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQSensors; +import java.sql.Types; + +/** + * QSensorsString is a Querydsl query type for QSensorsString + */ +public class QSensorsString extends AbstractQSensors { + + private static final long serialVersionUID = -2105995707; + private static final String TABLE_NAME = "SENSORS"; + + public static final QSensorsString SENSORS = new QSensorsString(TABLE_NAME); + + public final StringPath id = createString("id"); + + public QSensorsString(String variable) { + super(QSensorsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + @Override + public StringPath getId() { + return id; + } + + @Override + public QSensorsString newWithAlias(String variable) { + return new QSensorsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QThingsLocationsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QThingsLocationsString.java new file mode 100644 index 000000000..3af318e14 --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QThingsLocationsString.java @@ -0,0 +1,48 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import java.sql.Types; + +/** + * QThingsLocationsString is a Querydsl query type for QThingsLocationsString + */ +public class QThingsLocationsString extends AbstractQThingsLocations { + + private static final long serialVersionUID = -1058612763; + private static final String TABLE_NAME = "THINGS_LOCATIONS"; + + public static final QThingsLocationsString THINGSLOCATIONS = new QThingsLocationsString(TABLE_NAME); + + public final StringPath locationId = createString("locationId"); + + public final StringPath thingId = createString("thingId"); + + public QThingsLocationsString(String variable) { + super(QThingsLocationsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(locationId, ColumnMetadata.named("LOCATION_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + @Override + public StringPath getLocationId() { + return locationId; + } + + @Override + public StringPath getThingId() { + return thingId; + } + + @Override + public QThingsLocationsString newWithAlias(String variable) { + return new QThingsLocationsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QThingsString.java b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QThingsString.java new file mode 100644 index 000000000..f5dea820c --- /dev/null +++ b/FROST-Server.SQL.PGString/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/stringid/relationalpaths/QThingsString.java @@ -0,0 +1,40 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThings; +import java.sql.Types; + +/** + * QThingsString is a Querydsl query type for QThingsString + */ +public class QThingsString extends AbstractQThings { + + private static final long serialVersionUID = -1006520967; + private static final String TABLE_NAME = "THINGS"; + + public static final QThingsString THINGS = new QThingsString(TABLE_NAME); + + public final StringPath id = createString("id"); + + public QThingsString(String variable) { + super(QThingsString.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.VARCHAR).withSize(36).notNull()); + } + + @Override + public StringPath getId() { + return id; + } + + @Override + public QThingsString newWithAlias(String variable) { + return new QThingsString(variable); + } + +} diff --git a/FROST-Server.SQL.PGString/src/main/resources/liquibase/tablesString.xml b/FROST-Server.SQL.PGString/src/main/resources/liquibase/tablesString.xml index 84a5f52e5..7005b7215 100644 --- a/FROST-Server.SQL.PGString/src/main/resources/liquibase/tablesString.xml +++ b/FROST-Server.SQL.PGString/src/main/resources/liquibase/tablesString.xml @@ -458,4 +458,32 @@ + + alter table "DATASTREAMS" alter column "ID" type varchar + alter table "DATASTREAMS" alter column "SENSOR_ID" type varchar + alter table "DATASTREAMS" alter column "OBS_PROPERTY_ID" type varchar + alter table "DATASTREAMS" alter column "THING_ID" type varchar + alter table "FEATURES" alter column "ID" type varchar + alter table "HIST_LOCATIONS" alter column "ID" type varchar + alter table "HIST_LOCATIONS" alter column "THING_ID" type varchar + alter table "LOCATIONS" alter column "ID" type varchar + alter table "LOCATIONS" alter column "GEN_FOI_ID" type varchar + alter table "LOCATIONS_HIST_LOCATIONS" alter column "LOCATION_ID" type varchar + alter table "LOCATIONS_HIST_LOCATIONS" alter column "HIST_LOCATION_ID" type varchar + alter table "OBS_PROPERTIES" alter column "ID" type varchar + alter table "OBSERVATIONS" alter column "ID" type varchar + alter table "OBSERVATIONS" alter column "DATASTREAM_ID" type varchar + alter table "OBSERVATIONS" alter column "FEATURE_ID" type varchar + alter table "SENSORS" alter column "ID" type varchar + alter table "THINGS" alter column "ID" type varchar + alter table "THINGS_LOCATIONS" alter column "THING_ID" type varchar + alter table "THINGS_LOCATIONS" alter column "LOCATION_ID" type varchar + alter table "MULTI_DATASTREAMS" alter column "ID" type varchar + alter table "MULTI_DATASTREAMS" alter column "SENSOR_ID" type varchar + alter table "MULTI_DATASTREAMS" alter column "THING_ID" type varchar + alter table "MULTI_DATASTREAMS_OBS_PROPERTIES" alter column "MULTI_DATASTREAM_ID" type varchar + alter table "MULTI_DATASTREAMS_OBS_PROPERTIES" alter column "OBS_PROPERTY_ID" type varchar + alter table "OBSERVATIONS" alter column "MULTI_DATASTREAM_ID" type varchar + + diff --git a/FROST-Server.SQL.PGUuid/pom.xml b/FROST-Server.SQL.PGUuid/pom.xml index e3b49cb21..995a5ff48 100644 --- a/FROST-Server.SQL.PGUuid/pom.xml +++ b/FROST-Server.SQL.PGUuid/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT ../pom.xml FROST-Server.SQL.PGUuid @@ -52,11 +52,6 @@ ${postgis.version} provided - - com.h2database - h2 - ${h2.version} - org.slf4j slf4j-api @@ -156,232 +151,10 @@ - - org.liquibase - liquibase-maven-plugin - ${liquibase.version} - - ${project.basedir}/src/main/resources/liquibase/tablesUuid.xml - ${builddatabase.driver} - ${builddatabase.url} - ${builddatabase.username} - ${builddatabase.password} - false - - - - generate-sources - - update - - - - - - com.querydsl - querydsl-maven-plugin - ${querydsl.version} - - - - export - - - - - ${builddatabase.driver} - ${builddatabase.url} - ${builddatabase.username} - ${builddatabase.password} - - LOCATIONS_HIST_LOCATIONS,HIST_LOCATIONS,THINGS_LOCATIONS,LOCATIONS,OBSERVATIONS,FEATURES,DATASTREAMS,MULTI_DATASTREAMS,OBS_PROPERTIES,SENSORS,THINGS,MULTI_DATASTREAMS_OBS_PROPERTIES, - locations_hist_locations,hist_locations,things_locations,locations,observations,features,datastreams,multi_datastreams,obs_properties,sensors,things,multi_datastreams_obs_properties - - de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid - ${project.basedir}/target/generated-sources/java - true - - - - DATASTREAMS
- ID - java.util.UUID -
- - DATASTREAMS
- SENSOR_ID - java.util.UUID -
- - DATASTREAMS
- OBS_PROPERTY_ID - java.util.UUID -
- - DATASTREAMS
- THING_ID - java.util.UUID -
- - - FEATURES
- ID - java.util.UUID -
- - - HIST_LOCATIONS
- ID - java.util.UUID -
- - HIST_LOCATIONS
- THING_ID - java.util.UUID -
- - - LOCATIONS
- ID - java.util.UUID -
- - LOCATIONS
- GEN_FOI_ID - java.util.UUID -
- - - LOCATIONS_HIST_LOCATIONS
- LOCATION_ID - java.util.UUID -
- - LOCATIONS_HIST_LOCATIONS
- HIST_LOCATION_ID - java.util.UUID -
- - - OBS_PROPERTIES
- ID - java.util.UUID -
- - - OBSERVATIONS
- ID - java.util.UUID -
- - OBSERVATIONS
- DATASTREAM_ID - java.util.UUID -
- - OBSERVATIONS
- FEATURE_ID - java.util.UUID -
- - OBSERVATIONS
- MULTI_DATASTREAM_ID - java.util.UUID -
- - - SENSORS
- ID - java.util.UUID -
- - - THINGS
- ID - java.util.UUID -
- - THINGS
- GEN_FOI_ID - java.util.UUID -
- - - THINGS_LOCATIONS
- THING_ID - java.util.UUID -
- - THINGS_LOCATIONS
- LOCATION_ID - java.util.UUID -
- - - MULTI_DATASTREAMS
- ID - java.util.UUID -
- - MULTI_DATASTREAMS
- SENSOR_ID - java.util.UUID -
- - MULTI_DATASTREAMS
- THING_ID - java.util.UUID -
- - - MULTI_DATASTREAMS_OBS_PROPERTIES
- MULTI_DATASTREAM_ID - java.util.UUID -
- - MULTI_DATASTREAMS_OBS_PROPERTIES
- OBS_PROPERTY_ID - java.util.UUID -
-
-
- - - javax.annotation - javax.annotation-api - ${annotation-api.version} - - - com.mysema.codegen - codegen - ${codegen.version} - - - com.h2database - h2 - ${h2.version} - - - org.postgresql - postgresql - ${postgres.version} - - - net.postgis - postgis-jdbc - ${postgis.version} - - - ch.qos.logback - logback-classic - 1.1.3 - - -
org.apache.maven.plugins maven-compiler-plugin - 3.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/EntityInserter.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/EntityInserter.java deleted file mode 100644 index 5c459df56..000000000 --- a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/EntityInserter.java +++ /dev/null @@ -1,1594 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.uuidid; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.querydsl.core.Tuple; -import com.querydsl.core.dml.StoreClause; -import com.querydsl.core.types.dsl.DateTimePath; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.StringPath; -import com.querydsl.spatial.GeometryPath; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import com.querydsl.sql.dml.SQLInsertClause; -import com.querydsl.sql.dml.SQLUpdateClause; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.geojson.GeoJsonDeserializier; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; -import de.fraunhofer.iosb.ilt.sta.model.builder.DatastreamBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.FeatureOfInterestBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.MultiDatastreamBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.SensorBuilder; -import de.fraunhofer.iosb.ilt.sta.model.builder.ThingBuilder; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.custom.geojson.GeoJsonSerializer; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.ResultType; -import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; -import java.io.IOException; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.UUID; -import org.geojson.Crs; -import org.geojson.Feature; -import org.geojson.GeoJsonObject; -import org.geojson.jackson.CrsType; -import org.geolatte.common.dataformats.json.jackson.JsonException; -import org.geolatte.common.dataformats.json.jackson.JsonMapper; -import org.geolatte.geom.Geometry; -import org.joda.time.Interval; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - * @author selimnairb - */ -public class EntityInserter { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(EntityInserter.class); - private final PostgresPersistenceManagerUuid pm; - private ObjectMapper formatter; - - public EntityInserter(PostgresPersistenceManagerUuid pm) { - this.pm = pm; - } - - public boolean insertDatastream(Datastream ds) throws NoSuchEntityException, IncompleteEntityException { - // First check ObservedPropery, Sensor and Thing - ObservedProperty op = ds.getObservedProperty(); - entityExistsOrCreate(op); - - Sensor s = ds.getSensor(); - entityExistsOrCreate(s); - - Thing t = ds.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - - QDatastreams qd = QDatastreams.datastreams; - SQLInsertClause insert = qFactory.insert(qd); - insert.set(qd.name, ds.getName()); - insert.set(qd.description, ds.getDescription()); - insert.set(qd.observationType, ds.getObservationType()); - insert.set(qd.unitDefinition, ds.getUnitOfMeasurement().getDefinition()); - insert.set(qd.unitName, ds.getUnitOfMeasurement().getName()); - insert.set(qd.unitSymbol, ds.getUnitOfMeasurement().getSymbol()); - insert.set(qd.properties, objectToJson(ds.getProperties())); - - insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MAX.getMillis())); - insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MIN.getMillis())); - insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MAX.getMillis())); - insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MIN.getMillis())); - - insert.set(qd.obsPropertyId, (UUID) op.getId().getValue()); - insert.set(qd.sensorId, (UUID) s.getId().getValue()); - insert.set(qd.thingId, (UUID) t.getId().getValue()); - - UUID datastreamId = insert.executeWithKey(qd.id); - LOGGER.info("Inserted datastream. Created id = {}.", datastreamId); - ds.setId(new UuidId(datastreamId)); - - // Create Observations, if any. - for (Observation o : ds.getObservations()) { - o.setDatastream(new DatastreamBuilder().setId(ds.getId()).build()); - o.complete(); - pm.insert(o); - } - - return true; - } - - public boolean updateDatastream(Datastream d, UUID dsId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QDatastreams qd = QDatastreams.datastreams; - SQLUpdateClause update = qFactory.update(qd); - if (d.isSetName()) { - if (d.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qd.name, d.getName()); - } - if (d.isSetDescription()) { - if (d.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qd.description, d.getDescription()); - } - if (d.isSetObservationType()) { - if (d.getObservationType() == null) { - throw new IncompleteEntityException("observationType can not be null."); - } - update.set(qd.observationType, d.getObservationType()); - } - if (d.isSetProperties()) { - update.set(qd.properties, objectToJson(d.getProperties())); - } - if (d.isSetObservedProperty()) { - if (!entityExists(d.getObservedProperty())) { - throw new NoSuchEntityException("ObservedProperty with no id or not found."); - } - update.set(qd.obsPropertyId, (UUID) d.getObservedProperty().getId().getValue()); - } - if (d.isSetSensor()) { - if (!entityExists(d.getSensor())) { - throw new NoSuchEntityException("Sensor with no id or not found."); - } - update.set(qd.sensorId, (UUID) d.getSensor().getId().getValue()); - } - if (d.isSetThing()) { - if (!entityExists(d.getThing())) { - throw new NoSuchEntityException("Thing with no id or not found."); - } - update.set(qd.thingId, (UUID) d.getThing().getId().getValue()); - } - if (d.isSetUnitOfMeasurement()) { - if (d.getUnitOfMeasurement() == null) { - throw new IncompleteEntityException("unitOfMeasurement can not be null."); - } - UnitOfMeasurement uom = d.getUnitOfMeasurement(); - update.set(qd.unitDefinition, uom.getDefinition()); - update.set(qd.unitName, uom.getName()); - update.set(qd.unitSymbol, uom.getSymbol()); - } - - update.where(qd.id.eq(dsId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Datastream {} caused {} rows to change!", dsId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Observations to the Datastream. - for (Observation o : d.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - UUID obsId = (UUID) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.datastreamId, dsId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to Observation {}.", dsId, obsId); - } - } - - LOGGER.debug("Updated Datastream {}", dsId); - return true; - } - - public boolean insertMultiDatastream(MultiDatastream ds) throws NoSuchEntityException, IncompleteEntityException { - // First check Sensor and Thing - Sensor s = ds.getSensor(); - entityExistsOrCreate(s); - - Thing t = ds.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - - QMultiDatastreams qd = QMultiDatastreams.multiDatastreams; - SQLInsertClause insert = qFactory.insert(qd); - insert.set(qd.name, ds.getName()); - insert.set(qd.description, ds.getDescription()); - insert.set(qd.observationTypes, objectToJson(ds.getMultiObservationDataTypes())); - insert.set(qd.unitOfMeasurements, objectToJson(ds.getUnitOfMeasurements())); - insert.set(qd.properties, objectToJson(ds.getProperties())); - - insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MAX.getMillis())); - insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MIN.getMillis())); - insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MAX.getMillis())); - insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManagerUuid.DATETIME_MIN.getMillis())); - - insert.set(qd.sensorId, (UUID) s.getId().getValue()); - insert.set(qd.thingId, (UUID) t.getId().getValue()); - - UUID multiDatastreamId = insert.executeWithKey(qd.id); - LOGGER.info("Inserted multiDatastream. Created id = {}.", multiDatastreamId); - ds.setId(new UuidId(multiDatastreamId)); - - // Create new Locations, if any. - EntitySet ops = ds.getObservedProperties(); - int rank = 0; - for (ObservedProperty op : ops) { - entityExistsOrCreate(op); - UUID opId = (UUID) op.getId().getValue(); - - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - insert = qFactory.insert(qMdOp); - insert.set(qMdOp.multiDatastreamId, multiDatastreamId); - insert.set(qMdOp.obsPropertyId, opId); - insert.set(qMdOp.rank, rank); - insert.execute(); - LOGGER.debug("Linked MultiDatastream {} to ObservedProperty {} with rank {}.", multiDatastreamId, opId, rank); - rank++; - } - - // Create Observations, if any. - for (Observation o : ds.getObservations()) { - o.setMultiDatastream(new MultiDatastreamBuilder().setId(ds.getId()).build()); - o.complete(); - pm.insert(o); - } - - return true; - } - - public boolean updateMultiDatastream(MultiDatastream d, UUID dsId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QMultiDatastreams qd = QMultiDatastreams.multiDatastreams; - SQLUpdateClause update = qFactory.update(qd); - if (d.isSetName()) { - if (d.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qd.name, d.getName()); - } - if (d.isSetDescription()) { - if (d.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qd.description, d.getDescription()); - } - if (d.isSetProperties()) { - update.set(qd.properties, objectToJson(d.getProperties())); - } - - if (d.isSetSensor()) { - if (!entityExists(d.getSensor())) { - throw new NoSuchEntityException("Sensor with no id or not found."); - } - update.set(qd.sensorId, (UUID) d.getSensor().getId().getValue()); - } - if (d.isSetThing()) { - if (!entityExists(d.getThing())) { - throw new NoSuchEntityException("Thing with no id or not found."); - } - update.set(qd.thingId, (UUID) d.getThing().getId().getValue()); - } - - MultiDatastream original = (MultiDatastream) pm.getEntityById(null, EntityType.MultiDatastream, new UuidId(dsId)); - int countOrig = original.getMultiObservationDataTypes().size(); - - int countUom = countOrig; - if (d.isSetUnitOfMeasurements()) { - if (d.getUnitOfMeasurements() == null) { - throw new IncompleteEntityException("unitOfMeasurements can not be null."); - } - List uoms = d.getUnitOfMeasurements(); - countUom = uoms.size(); - update.set(qd.unitOfMeasurements, objectToJson(uoms)); - } - int countDataTypes = countOrig; - if (d.isSetMultiObservationDataTypes()) { - List dataTypes = d.getMultiObservationDataTypes(); - if (dataTypes == null) { - throw new IncompleteEntityException("multiObservationDataTypes can not be null."); - } - countDataTypes = dataTypes.size(); - update.set(qd.observationTypes, objectToJson(dataTypes)); - } - EntitySet ops = d.getObservedProperties(); - int countOps = countOrig + ops.size(); - for (ObservedProperty op : ops) { - if (op.getId() == null || !entityExists(op)) { - throw new NoSuchEntityException("ObservedProperty with no id or not found."); - } - } - - if (countUom != countDataTypes) { - throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of multiObservationDataTypes."); - } - if (countUom != countOps) { - throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of ObservedProperties."); - } - - update.where(qd.id.eq(dsId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Datastream {} caused {} rows to change!", dsId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing ObservedProperties to the MultiDatastream. - int rank = countOrig; - for (ObservedProperty op : ops) { - UUID opId = (UUID) op.getId().getValue(); - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - long oCount = qFactory.insert(qMdOp) - .set(qMdOp.multiDatastreamId, dsId) - .set(qMdOp.obsPropertyId, opId) - .set(qMdOp.rank, rank) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to ObservedProperty {} with rank {}.", dsId, opId, rank); - } - rank++; - } - - // Link existing Observations to the MultiDatastream. - for (Observation o : d.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - UUID obsId = (UUID) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.datastreamId, dsId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned datastream {} to Observation {}.", dsId, obsId); - } - } - - LOGGER.debug("Updated Datastream {}", dsId); - return true; - } - - public boolean insertFeatureOfInterest(FeatureOfInterest foi) throws NoSuchEntityException { - // No linked entities to check first. - SQLQueryFactory qFactory = pm.createQueryFactory(); - QFeatures qfoi = QFeatures.features; - SQLInsertClause insert = qFactory.insert(qfoi); - insert.set(qfoi.name, foi.getName()); - insert.set(qfoi.description, foi.getDescription()); - insert.set(qfoi.properties, objectToJson(foi.getProperties())); - - String encodingType = foi.getEncodingType(); - insert.set(qfoi.encodingType, encodingType); - insertGeometry(insert, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); - - UUID generatedId = insert.executeWithKey(qfoi.id); - LOGGER.info("Inserted FeatureOfInterest. Created id = {}.", generatedId); - foi.setId(new UuidId(generatedId)); - return true; - } - - public boolean updateFeatureOfInterest(FeatureOfInterest foi, UUID foiId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QFeatures qfoi = QFeatures.features; - SQLUpdateClause update = qFactory.update(qfoi); - if (foi.isSetName()) { - if (foi.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qfoi.name, foi.getName()); - } - if (foi.isSetDescription()) { - if (foi.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qfoi.description, foi.getDescription()); - } - if (foi.isSetProperties()) { - update.set(qfoi.properties, objectToJson(foi.getProperties())); - } - - if (foi.isSetEncodingType() && foi.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - if (foi.isSetFeature() && foi.getFeature() == null) { - throw new IncompleteEntityException("feature can not be null."); - } - if (foi.isSetEncodingType() && foi.getEncodingType() != null && foi.isSetFeature() && foi.getFeature() != null) { - String encodingType = foi.getEncodingType(); - update.set(qfoi.encodingType, encodingType); - insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); - } else if (foi.isSetEncodingType() && foi.getEncodingType() != null) { - String encodingType = foi.getEncodingType(); - update.set(qfoi.encodingType, encodingType); - } else if (foi.isSetFeature() && foi.getFeature() != null) { - String encodingType = qFactory.select(qfoi.encodingType) - .from(qfoi) - .where(qfoi.id.eq(foiId)) - .fetchFirst(); - Object parsedObject = reParseGeometry(encodingType, foi.getFeature()); - insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, parsedObject); - } - - update.where(qfoi.id.eq(foiId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating FeatureOfInterest {} caused {} rows to change!", foiId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Observations to the FeatureOfInterest. - for (Observation o : foi.getObservations()) { - if (o.getId() == null || !entityExists(o)) { - throw new NoSuchEntityException("Observation with no id or non existing."); - } - UUID obsId = (UUID) o.getId().getValue(); - QObservations qo = QObservations.observations; - long oCount = qFactory.update(qo) - .set(qo.featureId, foiId) - .where(qo.id.eq(obsId)) - .execute(); - if (oCount > 0) { - LOGGER.info("Assigned FeatureOfInterest {} to Observation {}.", foiId, obsId); - } - } - - LOGGER.debug("Updated FeatureOfInterest {}", foiId); - return true; - } - - public FeatureOfInterest generateFeatureOfInterest(Id datastreamId, boolean isMultiDatastream) throws NoSuchEntityException { - UUID dsId = (UUID) datastreamId.getValue(); - SQLQueryFactory qf = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - QThingsLocations qtl = QThingsLocations.thingsLocations; - QThings qt = QThings.things; - QDatastreams qd = QDatastreams.datastreams; - QMultiDatastreams qmd = QMultiDatastreams.multiDatastreams; - // TODO: Should probably contain a where that only returns locations - // with a supported encoding type. - SQLQuery query = qf.select(ql.id, ql.genFoiId) - .from(ql) - .innerJoin(qtl).on(ql.id.eq(qtl.locationId)) - .innerJoin(qt).on(qt.id.eq(qtl.thingId)); - if (isMultiDatastream) { - query.innerJoin(qmd).on(qmd.thingId.eq(qt.id)) - .where(qmd.id.eq(dsId)); - } else { - query.innerJoin(qd).on(qd.thingId.eq(qt.id)) - .where(qd.id.eq(dsId)); - } - Tuple tuple = query.fetchOne(); - if (tuple == null) { - // Can not generate foi from Thing with no locations. - throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); - } - UUID genFoiId = tuple.get(ql.genFoiId); - UUID locationId = tuple.get(ql.id); - - FeatureOfInterest foi; - if (genFoiId == null) { - query = qf.select(ql.id, ql.encodingType, ql.location) - .from(ql) - .where(ql.id.eq(locationId)); - tuple = query.fetchOne(); - if (tuple == null) { - // Can not generate foi from Thing with no locations. - // Should not happen, since the query succeeded just before. - throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); - } - String encoding = tuple.get(ql.encodingType); - String locString = tuple.get(ql.location); - Object locObject = PropertyHelper.locationFromEncoding(encoding, locString); - foi = new FeatureOfInterestBuilder() - .setName("FoI for location " + locationId) - .setDescription("Generated from location " + locationId) - .setEncodingType(encoding) - .setFeature(locObject) - .build(); - insertFeatureOfInterest(foi); - UUID foiId = (UUID) foi.getId().getValue(); - qf.update(ql) - .set(ql.genFoiId, (UUID) foi.getId().getValue()) - .where(ql.id.eq(locationId)) - .execute(); - LOGGER.debug("Generated foi {} from Location {}.", foiId, locationId); - } else { - foi = new FeatureOfInterest(); - foi.setId(new UuidId(genFoiId)); - } - return foi; - } - - public boolean insertHistoricalLocation(HistoricalLocation h) throws NoSuchEntityException, IncompleteEntityException { - Thing t = h.getThing(); - entityExistsOrCreate(t); - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QHistLocations qhl = QHistLocations.histLocations; - SQLInsertClause insert = qFactory.insert(qhl); - insert.set(qhl.time, new Timestamp(h.getTime().getDateTime().getMillis())); - insert.set(qhl.thingId, (UUID) h.getThing().getId().getValue()); - - UUID generatedId = insert.executeWithKey(qhl.id); - LOGGER.info("Inserted HistoricalLocation. Created id = {}.", generatedId); - h.setId(new UuidId(generatedId)); - - EntitySet locations = h.getLocations(); - for (Location l : locations) { - entityExistsOrCreate(l); - UUID lId = (UUID) l.getId().getValue(); - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, generatedId); - insert.set(qlhl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", lId, generatedId); - } - return true; - } - - public boolean updateHistoricalLocation(HistoricalLocation hl, UUID id) { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QHistLocations qhl = QHistLocations.histLocations; - SQLUpdateClause update = qFactory.update(qhl); - if (hl.isSetThing()) { - if (!entityExists(hl.getThing())) { - throw new IncompleteEntityException("Thing can not be null."); - } - update.set(qhl.thingId, (UUID) hl.getThing().getId().getValue()); - } - if (hl.isSetTime()) { - if (hl.getTime() == null) { - throw new IncompleteEntityException("time can not be null."); - } - insertTimeInstant(update, qhl.time, hl.getTime()); - } - update.where(qhl.id.eq(id)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Location {} caused {} rows to change!", id, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Location {}", id); - - // Link existing locations to the HistoricalLocation. - for (Location l : hl.getLocations()) { - if (!entityExists(l)) { - throw new IllegalArgumentException("Unknown Location or Location with no id."); - } - UUID lId = (UUID) l.getId().getValue(); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - SQLInsertClause insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, id); - insert.set(qlhl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", lId, id); - } - return true; - } - - public boolean insertLocation(Location l) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - SQLInsertClause insert = qFactory.insert(ql); - insert.set(ql.name, l.getName()); - insert.set(ql.description, l.getDescription()); - insert.set(ql.properties, objectToJson(l.getProperties())); - - String encodingType = l.getEncodingType(); - insert.set(ql.encodingType, encodingType); - insertGeometry(insert, ql.location, ql.geom, encodingType, l.getLocation()); - - UUID locationId = insert.executeWithKey(ql.id); - LOGGER.info("Inserted Location. Created id = {}.", locationId); - l.setId(new UuidId(locationId)); - - // Link Things - EntitySet things = l.getThings(); - for (Thing t : things) { - entityExistsOrCreate(t); - UUID thingId = (UUID) t.getId().getValue(); - - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - long count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new Location to thing. - insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - - // Create HistoricalLocation for Thing - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - UUID histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - // Link Location to HistoricalLocation. - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locationId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locationId, histLocationId); - } - - return true; - } - - public boolean updateLocation(Location l, UUID locationId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QLocations ql = QLocations.locations; - SQLUpdateClause update = qFactory.update(ql); - if (l.isSetName()) { - if (l.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(ql.name, l.getName()); - } - if (l.isSetDescription()) { - if (l.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(ql.description, l.getDescription()); - } - if (l.isSetProperties()) { - update.set(ql.properties, objectToJson(l.getProperties())); - } - - if (l.isSetEncodingType() && l.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - if (l.isSetLocation() && l.getLocation() == null) { - throw new IncompleteEntityException("locations can not be null."); - } - if (l.isSetEncodingType() && l.getEncodingType() != null && l.isSetLocation() && l.getLocation() != null) { - String encodingType = l.getEncodingType(); - update.set(ql.encodingType, encodingType); - insertGeometry(update, ql.location, ql.geom, encodingType, l.getLocation()); - } else if (l.isSetEncodingType() && l.getEncodingType() != null) { - String encodingType = l.getEncodingType(); - update.set(ql.encodingType, encodingType); - } else if (l.isSetLocation() && l.getLocation() != null) { - String encodingType = qFactory.select(ql.encodingType) - .from(ql) - .where(ql.id.eq(locationId)) - .fetchFirst(); - Object parsedObject = reParseGeometry(encodingType, l.getLocation()); - insertGeometry(update, ql.location, ql.geom, encodingType, parsedObject); - } - - update.where(ql.id.eq(locationId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Location {} caused {} rows to change!", locationId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Location {}", locationId); - - // Link HistoricalLocation. - for (HistoricalLocation hl : l.getHistoricalLocations()) { - if (hl.getId() == null) { - throw new IllegalArgumentException("HistoricalLocation with no id."); - } - UUID hlId = (UUID) hl.getId().getValue(); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - SQLInsertClause insert = qFactory.insert(qlhl); - insert.set(qlhl.histLocationId, hlId); - insert.set(qlhl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to HistoricalLocation {}.", locationId, hlId); - } - - // Link Things - EntitySet things = l.getThings(); - for (Thing t : things) { - if (!entityExists(t)) { - throw new NoSuchEntityException("Thing not found."); - } - UUID thingId = (UUID) t.getId().getValue(); - - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new Location to thing. - SQLInsertClause insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - - // Create HistoricalLocation for Thing - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - UUID histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - // Link Location to HistoricalLocation. - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locationId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locationId, histLocationId); - } - return true; - } - - public boolean insertObservation(Observation o) throws NoSuchEntityException, IncompleteEntityException { - Datastream ds = o.getDatastream(); - MultiDatastream mds = o.getMultiDatastream(); - Id streamId; - boolean isMultiDatastream = false; - if (ds != null) { - entityExistsOrCreate(ds); - streamId = ds.getId(); - } else if (mds != null) { - entityExistsOrCreate(mds); - streamId = mds.getId(); - isMultiDatastream = true; - } else { - throw new IncompleteEntityException("Missing Datastream or MultiDatastream."); - } - - FeatureOfInterest f = o.getFeatureOfInterest(); - if (f == null) { - f = generateFeatureOfInterest(streamId, isMultiDatastream); - } else { - entityExistsOrCreate(f); - } - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObservations qo = QObservations.observations; - SQLInsertClause insert = qFactory.insert(qo); - - insert.set(qo.parameters, objectToJson(o.getParameters())); - TimeValue phenomenonTime = o.getPhenomenonTime(); - if (phenomenonTime == null) { - phenomenonTime = TimeInstant.now(); - } - insertTimeValue(insert, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, phenomenonTime); - insertTimeInstant(insert, qo.resultTime, o.getResultTime()); - insertTimeInterval(insert, qo.validTimeStart, qo.validTimeEnd, o.getValidTime()); - - Object result = o.getResult(); - if (isMultiDatastream) { - if (!(result instanceof List)) { - throw new IllegalArgumentException("Multidatastream only accepts array results."); - } - List list = (List) result; - ResourcePath path = mds.getPath(); - path.addPathElement(new EntitySetPathElement(EntityType.ObservedProperty, null), false, false); - long count = pm.count(path, null); - if (count != list.size()) { - throw new IllegalArgumentException("Size of result array (" + list.size() + ") must match number of observed properties (" + count + ") in the MultiDatastream."); - } - } - - if (result instanceof Number) { - insert.set(qo.resultType, ResultType.NUMBER.sqlValue()); - insert.set(qo.resultString, result.toString()); - insert.set(qo.resultNumber, ((Number) result).doubleValue()); - } else if (result instanceof Boolean) { - insert.set(qo.resultType, ResultType.BOOLEAN.sqlValue()); - insert.set(qo.resultString, result.toString()); - insert.set(qo.resultBoolean, (Boolean) result); - } else if (result instanceof String) { - insert.set(qo.resultType, ResultType.STRING.sqlValue()); - insert.set(qo.resultString, result.toString()); - } else { - insert.set(qo.resultType, ResultType.OBJECT_ARRAY.sqlValue()); - insert.set(qo.resultJson, objectToJson(result)); - } - - if (o.getResultQuality() != null) { - insert.set(qo.resultQuality, o.getResultQuality().toString()); - } - if (ds != null) { - insert.set(qo.datastreamId, (UUID) ds.getId().getValue()); - } - if (mds != null) { - insert.set(qo.multiDatastreamId, (UUID) mds.getId().getValue()); - } - insert.set(qo.featureId, (UUID) f.getId().getValue()); - - UUID generatedId = insert.executeWithKey(qo.id); - LOGGER.debug("Inserted Observation. Created id = {}.", generatedId); - o.setId(new UuidId(generatedId)); - return true; - } - - public boolean updateObservation(Observation o, UUID id) { - Observation oldObservation = (Observation) pm.getEntityById(null, EntityType.Observation, new UuidId(id)); - Datastream ds = oldObservation.getDatastream(); - MultiDatastream mds = oldObservation.getMultiDatastream(); - boolean newHasDatastream = ds != null; - boolean newHasMultiDatastream = mds != null; - - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObservations qo = QObservations.observations; - SQLUpdateClause update = qFactory.update(qo); - if (o.isSetDatastream()) { - if (o.getDatastream() == null) { - newHasDatastream = false; - update.setNull(qo.datastreamId); - } else { - if (!entityExists(o.getDatastream())) { - throw new IncompleteEntityException("Datastream not found."); - } - newHasDatastream = true; - ds = o.getDatastream(); - update.set(qo.datastreamId, (UUID) ds.getId().getValue()); - } - } - if (o.isSetMultiDatastream()) { - mds = o.getMultiDatastream(); - if (mds == null) { - newHasMultiDatastream = false; - update.setNull(qo.multiDatastreamId); - } else { - if (!entityExists(mds)) { - throw new IncompleteEntityException("MultiDatastream not found."); - } - newHasMultiDatastream = true; - update.set(qo.multiDatastreamId, (UUID) mds.getId().getValue()); - } - } - if (newHasDatastream == newHasMultiDatastream) { - throw new IllegalArgumentException("Observation must have either a Datastream or a MultiDatastream."); - } - if (o.isSetFeatureOfInterest()) { - if (!entityExists(o.getFeatureOfInterest())) { - throw new IncompleteEntityException("FeatureOfInterest not found."); - } - update.set(qo.featureId, (UUID) o.getFeatureOfInterest().getId().getValue()); - } - if (o.isSetParameters()) { - update.set(qo.parameters, objectToJson(o.getParameters())); - } - if (o.isSetPhenomenonTime()) { - if (o.getPhenomenonTime() == null) { - throw new IncompleteEntityException("phenomenonTime can not be null."); - } - insertTimeValue(update, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, o.getPhenomenonTime()); - } - - if (o.isSetResult() && o.getResult() != null) { - Object result = o.getResult(); - if (newHasMultiDatastream) { - if (!(result instanceof List)) { - throw new IllegalArgumentException("Multidatastream only accepts array results."); - } - List list = (List) result; - ResourcePath path = mds.getPath(); - path.addPathElement(new EntitySetPathElement(EntityType.ObservedProperty, null), false, false); - long count = pm.count(path, null); - if (count != list.size()) { - throw new IllegalArgumentException("Size of result array (" + list.size() + ") must match number of observed properties (" + count + ") in the MultiDatastream."); - } - } - if (result instanceof Number) { - update.set(qo.resultType, ResultType.NUMBER.sqlValue()); - update.set(qo.resultString, result.toString()); - update.set(qo.resultNumber, ((Number) result).doubleValue()); - update.setNull(qo.resultBoolean); - update.setNull(qo.resultJson); - } else if (result instanceof Boolean) { - update.set(qo.resultType, ResultType.BOOLEAN.sqlValue()); - update.set(qo.resultString, result.toString()); - update.set(qo.resultBoolean, (Boolean) result); - update.setNull(qo.resultNumber); - update.setNull(qo.resultJson); - } else if (result instanceof String) { - update.set(qo.resultType, ResultType.STRING.sqlValue()); - update.set(qo.resultString, result.toString()); - update.setNull(qo.resultNumber); - update.setNull(qo.resultBoolean); - update.setNull(qo.resultJson); - } else { - update.set(qo.resultType, ResultType.OBJECT_ARRAY.sqlValue()); - update.set(qo.resultJson, objectToJson(result)); - update.setNull(qo.resultString); - update.setNull(qo.resultNumber); - update.setNull(qo.resultBoolean); - } - } - - if (o.isSetResultQuality()) { - update.set(qo.resultQuality, objectToJson(o.getResultQuality())); - } - if (o.isSetResultTime()) { - insertTimeInstant(update, qo.resultTime, o.getResultTime()); - } - if (o.isSetValidTime()) { - insertTimeInterval(update, qo.validTimeStart, qo.validTimeEnd, o.getValidTime()); - } - update.where(qo.id.eq(id)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Observation {} caused {} rows to change!", id, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Observation {}", id); - return true; - } - - public boolean insertObservedProperty(ObservedProperty op) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObsProperties qop = QObsProperties.obsProperties; - SQLInsertClause insert = qFactory.insert(qop); - insert.set(qop.definition, op.getDefinition()); - insert.set(qop.name, op.getName()); - insert.set(qop.description, op.getDescription()); - insert.set(qop.properties, objectToJson(op.getProperties())); - - UUID generatedId = insert.executeWithKey(qop.id); - LOGGER.info("Inserted ObservedProperty. Created id = {}.", generatedId); - op.setId(new UuidId(generatedId)); - - // Create new datastreams, if any. - for (Datastream ds : op.getDatastreams()) { - ds.setSensor(new SensorBuilder().setId(op.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : op.getMultiDatastreams()) { - mds.setSensor(new SensorBuilder().setId(op.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - return true; - } - - public boolean updateObservedProperty(ObservedProperty op, UUID opId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QObsProperties qop = QObsProperties.obsProperties; - SQLUpdateClause update = qFactory.update(qop); - if (op.isSetDefinition()) { - if (op.getDefinition() == null) { - throw new IncompleteEntityException("definition can not be null."); - } - update.set(qop.definition, op.getDefinition()); - } - if (op.isSetDescription()) { - if (op.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qop.description, op.getDescription()); - } - if (op.isSetName()) { - if (op.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qop.name, op.getName()); - } - if (op.isSetProperties()) { - update.set(qop.properties, objectToJson(op.getProperties())); - } - - update.where(qop.id.eq(opId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating ObservedProperty {} caused {} rows to change!", opId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Datastreams to the observedProperty. - for (Datastream ds : op.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("ObservedProperty with no id or non existing."); - } - UUID dsId = (UUID) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.obsPropertyId, opId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to ObservedProperty {}.", dsId, opId); - } - } - - if (!op.getMultiDatastreams().isEmpty()) { - throw new IllegalArgumentException("Can not add MultiDatastreams to an ObservedProperty."); - } - - LOGGER.debug("Updated ObservedProperty {}", opId); - return true; - } - - public boolean insertSensor(Sensor s) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QSensors qs = QSensors.sensors; - SQLInsertClause insert = qFactory.insert(qs); - insert.set(qs.name, s.getName()); - insert.set(qs.description, s.getDescription()); - insert.set(qs.encodingType, s.getEncodingType()); - // TODO: Check metadata serialisation. - insert.set(qs.metadata, s.getMetadata().toString()); - insert.set(qs.properties, objectToJson(s.getProperties())); - - UUID generatedId = insert.executeWithKey(qs.id); - LOGGER.info("Inserted Sensor. Created id = {}.", generatedId); - s.setId(new UuidId(generatedId)); - - // Create new datastreams, if any. - for (Datastream ds : s.getDatastreams()) { - ds.setSensor(new SensorBuilder().setId(s.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : s.getMultiDatastreams()) { - mds.setSensor(new SensorBuilder().setId(s.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - return true; - } - - public boolean updateSensor(Sensor s, UUID sensorId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QSensors qs = QSensors.sensors; - SQLUpdateClause update = qFactory.update(qs); - if (s.isSetName()) { - if (s.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qs.name, s.getName()); - } - if (s.isSetDescription()) { - if (s.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qs.description, s.getDescription()); - } - if (s.isSetEncodingType()) { - if (s.getEncodingType() == null) { - throw new IncompleteEntityException("encodingType can not be null."); - } - update.set(qs.encodingType, s.getEncodingType()); - } - if (s.isSetMetadata()) { - if (s.getMetadata() == null) { - throw new IncompleteEntityException("metadata can not be null."); - } - // TODO: Check metadata serialisation. - update.set(qs.metadata, s.getMetadata().toString()); - } - if (s.isSetProperties()) { - update.set(qs.properties, objectToJson(s.getProperties())); - } - - update.where(qs.id.eq(sensorId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Sensor {} caused {} rows to change!", sensorId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - - // Link existing Datastreams to the sensor. - for (Datastream ds : s.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("Datastream with no id or non existing."); - } - UUID dsId = (UUID) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.sensorId, sensorId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to sensor {}.", dsId, sensorId); - } - } - - // Link existing MultiDatastreams to the sensor. - for (MultiDatastream mds : s.getMultiDatastreams()) { - if (mds.getId() == null || !entityExists(mds)) { - throw new NoSuchEntityException("MultiDatastream with no id or non existing."); - } - UUID mdsId = (UUID) mds.getId().getValue(); - QMultiDatastreams qmds = QMultiDatastreams.multiDatastreams; - long mdsCount = qFactory.update(qmds) - .set(qmds.sensorId, sensorId) - .where(qmds.id.eq(mdsId)) - .execute(); - if (mdsCount > 0) { - LOGGER.info("Assigned multiDatastream {} to sensor {}.", mdsId, sensorId); - } - } - - LOGGER.debug("Updated Sensor {}", sensorId); - return true; - } - - public boolean insertThing(Thing t) throws NoSuchEntityException, IncompleteEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QThings qt = QThings.things; - SQLInsertClause insert = qFactory.insert(qt); - insert.set(qt.name, t.getName()); - insert.set(qt.description, t.getDescription()); - insert.set(qt.properties, objectToJson(t.getProperties())); - - UUID thingId = insert.executeWithKey(qt.id); - LOGGER.info("Inserted Thing. Created id = {}.", thingId); - t.setId(new UuidId(thingId)); - - // Create new Locations, if any. - List locationIds = new ArrayList<>(); - for (Location l : t.getLocations()) { - entityExistsOrCreate(l); - UUID lId = (UUID) l.getId().getValue(); - - QThingsLocations qtl = QThingsLocations.thingsLocations; - insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, lId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", lId, thingId); - locationIds.add(lId); - } - - // Now link the new locations also to a historicalLocation. - if (!locationIds.isEmpty()) { - QHistLocations qhl = QHistLocations.histLocations; - insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - UUID histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - for (UUID locId : locationIds) { - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locId, histLocationId); - } - } - - // Create new datastreams, if any. - for (Datastream ds : t.getDatastreams()) { - ds.setThing(new ThingBuilder().setId(t.getId()).build()); - ds.complete(); - pm.insert(ds); - } - - // Create new multiDatastreams, if any. - for (MultiDatastream mds : t.getMultiDatastreams()) { - mds.setThing(new ThingBuilder().setId(t.getId()).build()); - mds.complete(); - pm.insert(mds); - } - - // TODO: if we allow the creation of historicalLocations through Things - // then we have to be able to link those to Locations we might have just created. - // However, id juggling will be needed! - return true; - } - - public boolean updateThing(Thing t, UUID thingId) throws NoSuchEntityException { - SQLQueryFactory qFactory = pm.createQueryFactory(); - QThings qt = QThings.things; - SQLUpdateClause update = qFactory.update(qt); - if (t.isSetName()) { - if (t.getName() == null) { - throw new IncompleteEntityException("name can not be null."); - } - update.set(qt.name, t.getName()); - } - if (t.isSetDescription()) { - if (t.getDescription() == null) { - throw new IncompleteEntityException("description can not be null."); - } - update.set(qt.description, t.getDescription()); - } - if (t.isSetProperties()) { - update.set(qt.properties, objectToJson(t.getProperties())); - } - update.where(qt.id.eq(thingId)); - long count = 0; - if (!update.isEmpty()) { - count = update.execute(); - } - if (count > 1) { - LOGGER.error("Updating Thing {} caused {} rows to change!", thingId, count); - throw new IllegalStateException("Update changed multiple rows."); - } - LOGGER.debug("Updated Thing {}", thingId); - - // Link existing Datastreams to the thing. - for (Datastream ds : t.getDatastreams()) { - if (ds.getId() == null || !entityExists(ds)) { - throw new NoSuchEntityException("Datastream with no id or non existing."); - } - UUID dsId = (UUID) ds.getId().getValue(); - QDatastreams qds = QDatastreams.datastreams; - long dsCount = qFactory.update(qds) - .set(qds.thingId, thingId) - .where(qds.id.eq(dsId)) - .execute(); - if (dsCount > 0) { - LOGGER.info("Assigned datastream {} to thing {}.", dsId, thingId); - } - } - - // Link existing MultiDatastreams to the thing. - for (MultiDatastream mds : t.getMultiDatastreams()) { - if (mds.getId() == null || !entityExists(mds)) { - throw new NoSuchEntityException("MultiDatastream with no id or non existing."); - } - UUID mdsId = (UUID) mds.getId().getValue(); - QMultiDatastreams qmds = QMultiDatastreams.multiDatastreams; - long mdsCount = qFactory.update(qmds) - .set(qmds.thingId, thingId) - .where(qmds.id.eq(mdsId)) - .execute(); - if (mdsCount > 0) { - LOGGER.info("Assigned multiDatastream {} to thing {}.", mdsId, thingId); - } - } - - // Link existing locations to the thing. - if (!t.getLocations().isEmpty()) { - // Unlink old Locations from Thing. - QThingsLocations qtl = QThingsLocations.thingsLocations; - count = qFactory.delete(qtl).where(qtl.thingId.eq(thingId)).execute(); - LOGGER.info("Unlinked {} locations from Thing {}.", count, thingId); - - // Link new locations to Thing, track the ids. - List locationIds = new ArrayList<>(); - for (Location l : t.getLocations()) { - if (l.getId() == null || !entityExists(l)) { - throw new NoSuchEntityException("Location with no id."); - } - UUID locationId = (UUID) l.getId().getValue(); - - SQLInsertClause insert = qFactory.insert(qtl); - insert.set(qtl.thingId, thingId); - insert.set(qtl.locationId, locationId); - insert.execute(); - LOGGER.debug("Linked Location {} to Thing {}.", locationId, thingId); - locationIds.add(locationId); - } - - // Now link the newly linked locations also to a historicalLocation. - if (!locationIds.isEmpty()) { - QHistLocations qhl = QHistLocations.histLocations; - SQLInsertClause insert = qFactory.insert(qhl); - insert.set(qhl.thingId, thingId); - insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); - UUID histLocationId = insert.executeWithKey(qhl.id); - LOGGER.debug("Created historicalLocation {}", histLocationId); - - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - for (UUID locId : locationIds) { - qFactory.insert(qlhl) - .set(qlhl.histLocationId, histLocationId) - .set(qlhl.locationId, locId) - .execute(); - LOGGER.info("Linked location {} to historicalLocation {}.", locId, histLocationId); - } - } - } - return true; - } - - private static T insertTimeValue(T clause, DateTimePath startPath, DateTimePath endPath, TimeValue time) { - if (time instanceof TimeInstant) { - TimeInstant timeInstant = (TimeInstant) time; - insertTimeInstant(clause, endPath, timeInstant); - return insertTimeInstant(clause, startPath, timeInstant); - } else if (time instanceof TimeInterval) { - TimeInterval timeInterval = (TimeInterval) time; - return insertTimeInterval(clause, startPath, endPath, timeInterval); - } - return clause; - } - - private static T insertTimeInstant(T clause, DateTimePath path, TimeInstant time) { - if (time == null) { - return clause; - } - clause.set(path, new Timestamp(time.getDateTime().getMillis())); - return clause; - } - - private static T insertTimeInterval(T clause, DateTimePath startPath, DateTimePath endPath, TimeInterval time) { - if (time == null) { - return clause; - } - Interval interval = time.getInterval(); - clause.set(startPath, new Timestamp(interval.getStartMillis())); - clause.set(endPath, new Timestamp(interval.getEndMillis())); - return clause; - } - - /** - * Sets both the geometry and location in the clause. - * - * @param The type of the clause. - * @param clause The insert or update clause to add to. - * @param locationPath The path to the location column. - * @param geomPath The path to the geometry column. - * @param encodingType The encoding type. - * @param location The location. - * @return The insert or update clause. - */ - private T insertGeometry(T clause, StringPath locationPath, GeometryPath geomPath, String encodingType, final Object location) { - if (encodingType != null && GeoJsonDeserializier.encodings.contains(encodingType.toLowerCase())) { - String locJson; - try { - locJson = new GeoJsonSerializer().serialize(location); - } catch (JsonProcessingException ex) { - LOGGER.error("Failed to store.", ex); - throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); - } - - // Postgres does not support Feature. - Object geoLocation = location; - if (location instanceof Feature) { - geoLocation = ((Feature) location).getGeometry(); - } - // Ensure the geoJson has a crs, otherwise Postgres complains. - if (geoLocation instanceof GeoJsonObject) { - GeoJsonObject geoJsonObject = (GeoJsonObject) geoLocation; - Crs crs = geoJsonObject.getCrs(); - if (crs == null) { - crs = new Crs(); - crs.setType(CrsType.name); - crs.getProperties().put("name", "EPSG:4326"); - geoJsonObject.setCrs(crs); - } - } - String geoJson; - try { - geoJson = new GeoJsonSerializer().serialize(geoLocation); - } catch (JsonProcessingException ex) { - LOGGER.error("Failed to store.", ex); - throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); - } - - try { - // geojson.jackson allows invalid polygons, geolatte catches those. - new JsonMapper().fromJson(geoJson, Geometry.class); - } catch (JsonException ex) { - throw new IllegalArgumentException("Invalid geoJson: " + ex.getMessage()); - } - clause.set(geomPath, Expressions.template(Geometry.class, "ST_Force2D(ST_Transform(ST_GeomFromGeoJSON({0}), 4326))", geoJson)); - clause.set(locationPath, locJson); - } else { - String json; - json = objectToJson(location); - clause.setNull(geomPath); - clause.set(locationPath, json); - } - return clause; - } - - private Object reParseGeometry(String encodingType, Object object) { - String json = objectToJson(object); - return PropertyHelper.locationFromEncoding(encodingType, json); - } - - /** - * Throws an exception if the entity has an id, but does not exist or if the - * entity can not be created. - * - * @param pm the persistenceManager - * @param e The Entity to check. - * @throws NoSuchEntityException If the entity has an id, but does not - * exist. - * @throws IncompleteEntityException If the entity has no id, but is not - * complete and can thus not be created. - */ - private void entityExistsOrCreate(Entity e) throws NoSuchEntityException, IncompleteEntityException { - if (e != null && e.getId() == null) { - e.complete(); - pm.insert(e); - } else if (e == null || !entityExists(e)) { - if (e == null) { - throw new NoSuchEntityException("No entity!"); - } - throw new NoSuchEntityException("No such entity '" + e.getEntityType() + "' with id " + e.getId().getValue()); - } - } - - public boolean entityExists(Entity e) { - if (e == null || e.getId() == null) { - return false; - } - UUID id = (UUID) e.getId().getValue(); - SQLQueryFactory qFactory = pm.createQueryFactory(); - long count = 0; - switch (e.getEntityType()) { - case Datastream: - QDatastreams d = QDatastreams.datastreams; - count = qFactory.select() - .from(d) - .where(d.id.eq(id)) - .fetchCount(); - break; - - case MultiDatastream: - QMultiDatastreams md = QMultiDatastreams.multiDatastreams; - count = qFactory.select() - .from(md) - .where(md.id.eq(id)) - .fetchCount(); - break; - - case FeatureOfInterest: - QFeatures foi = QFeatures.features; - count = qFactory.select() - .from(foi) - .where(foi.id.eq(id)) - .fetchCount(); - break; - - case HistoricalLocation: - QHistLocations h = QHistLocations.histLocations; - count = qFactory.select() - .from(h) - .where(h.id.eq(id)) - .fetchCount(); - break; - - case Location: - QLocations l = QLocations.locations; - count = qFactory.select() - .from(l) - .where(l.id.eq(id)) - .fetchCount(); - break; - - case Observation: - QObservations o = QObservations.observations; - count = qFactory.select() - .from(o) - .where(o.id.eq(id)) - .fetchCount(); - break; - - case ObservedProperty: - QObsProperties op = QObsProperties.obsProperties; - count = qFactory.select() - .from(op) - .where(op.id.eq(id)) - .fetchCount(); - break; - - case Sensor: - QSensors s = QSensors.sensors; - count = qFactory.select() - .from(s) - .where(s.id.eq(id)) - .fetchCount(); - break; - - case Thing: - QThings t = QThings.things; - count = qFactory.select() - .from(t) - .where(t.id.eq(id)) - .fetchCount(); - break; - - default: - throw new AssertionError(e.getEntityType().name()); - } - if (count > 1) { - LOGGER.error("More than one instance of {} with id {}.", e.getEntityType(), id); - } - return count > 0; - } - - public boolean entityExists(ResourcePath path) { - long count = pm.count(path, null); - if (count > 1) { - LOGGER.error("More than one instance of {}", path.toString()); - } - return count > 0; - } - - public String objectToJson(Object object) { - if (object == null) { - return null; - } - try { - return getFormatter().writeValueAsString(object); - } catch (IOException ex) { - throw new IllegalStateException("Could not serialise object.", ex); - } - } - - public ObjectMapper getFormatter() { - if (formatter == null) { - formatter = new ObjectMapper(); - } - return formatter; - } - -} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/IdGenerationHandlerUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/IdGenerationHandlerUuid.java new file mode 100644 index 000000000..f7db29212 --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/IdGenerationHandlerUuid.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * Copyright (C) 2018 KIT TECO, Vincenz-Prießnitz-Str. 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.sta.persistence.postgres.uuidid; + +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.IdGenerationHandler; + +/** + * + * Class for handling the persistence setting "idGenerationMode". + * + * @author koepke, scf + */ +public class IdGenerationHandlerUuid extends IdGenerationHandler { + + /** + * Constructor for IdGenerationHandler. + * + * @param entity Entity for which idGenerationMode should be + * checked/applied. + */ + public IdGenerationHandlerUuid(Entity entity) { + super(entity); + } + + /** + * + * Modify the entity id. + * + */ + @Override + public void modifyClientSuppliedId() { + // Nothing to do for now. + } + + /** + * + * Checks if a client generated id is valid. + * + * @return true if client generated id is valid. + */ + @Override + protected boolean validateClientSuppliedId() { + return getIdValue() != null; + } +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/IdManagerUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/IdManagerUuid.java new file mode 100644 index 000000000..ae6009e78 --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/IdManagerUuid.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.uuidid; + +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; +import java.util.UUID; + +/** + * + * @author scf + */ +class IdManagerUuid implements IdManager { + + @Override + public Class getIdClass() { + return UuidId.class; + } + + @Override + public UuidId parseId(String input) { + if (input.startsWith("'")) { + return new UuidId(input.substring(1, input.length() - 1)); + } + return new UuidId(input); + } + + @Override + public Id fromObject(Object input) { + if (input instanceof UUID) { + return new UuidId((UUID) input); + } + throw new IllegalArgumentException("Can not use " + input.getClass().getName() + " (" + input + ") as a UUID Id"); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PathSqlBuilderUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PathSqlBuilderUuid.java deleted file mode 100644 index 510a6045c..000000000 --- a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PathSqlBuilderUuid.java +++ /dev/null @@ -1,667 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.uuidid; - -import com.querydsl.core.Tuple; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.ComparablePath; -import com.querydsl.sql.RelationalPathBase; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyArrayIndex; -import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PathSqlBuilder; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; -import de.fraunhofer.iosb.ilt.sta.query.OrderBy; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.settings.PersistenceSettings; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - * @author selimnairb - */ -public class PathSqlBuilderUuid implements PathSqlBuilder { - - public static class TableRefString implements TableRef { - - public EntityType type; - public RelationalPathBase qPath; - public ComparablePath idPath; - - public TableRefString() { - } - - public TableRefString(TableRefString source) { - type = source.type; - qPath = source.qPath; - idPath = source.idPath; - } - - @Override - public EntityType getType() { - return type; - } - - @Override - public RelationalPathBase getqPath() { - return qPath; - } - - @Override - public void clear() { - type = null; - qPath = null; - idPath = null; - } - - @Override - public TableRef copy() { - TableRefString copy = new TableRefString(this); - return copy; - } - - @Override - public boolean isEmpty() { - return type == null && qPath == null; - } - } - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PathSqlBuilderUuid.class); - /** - * The prefix used for table aliases. The main entity is always - * <PREFIX>1. - */ - public static final String ALIAS_PREFIX = "e"; - private SQLQueryFactory queryFactory; - private SQLQuery sqlQuery; - private Set selectedProperties; - private final TableRefString lastPath = new TableRefString(); - private TableRefString mainTable; - private int aliasNr = 0; - private boolean isFilter = false; - private boolean needsDistinct = false; - - @Override - public synchronized SQLQuery buildFor(ResourcePath path, Query query, SQLQueryFactory sqlQueryFactory, PersistenceSettings settings) { - this.queryFactory = sqlQueryFactory; - selectedProperties = new HashSet<>(); - sqlQuery = queryFactory.select(new Expression[]{}); - lastPath.clear(); - aliasNr = 0; - List elements = new ArrayList<>(path.getPathElements()); - - int count = elements.size(); - for (int i = count - 1; i >= 0; i--) { - ResourcePathElement element = elements.get(i); - element.visit(this); - } - - if (query != null) { - boolean distict = false; - PgExpressionHandler handler = new PgExpressionHandler(this, mainTable.copy()); - for (OrderBy ob : query.getOrderBy()) { - handler.addOrderbyToQuery(ob, sqlQuery); - } - if (needsDistinct) { - sqlQuery.distinct(); - distict = true; - } - isFilter = true; - needsDistinct = false; - de.fraunhofer.iosb.ilt.sta.query.expression.Expression filter = query.getFilter(); - if (filter != null) { - handler.addFilterToQuery(filter, sqlQuery); - } - if (settings.getAlwaysOrderbyId()) { - sqlQuery.orderBy(mainTable.idPath.asc()); - } - if (needsDistinct && !distict) { - sqlQuery.distinct(); - } - } - - return sqlQuery; - } - - @Override - public void visit(EntityPathElement element) { - queryEntityType(element.getEntityType(), element.getId(), lastPath); - } - - @Override - public void visit(EntitySetPathElement element) { - queryEntityType(element.getEntityType(), null, lastPath); - } - - @Override - public void visit(PropertyPathElement element) { - selectedProperties.add(element.getProperty()); - selectedProperties.add(EntityProperty.Id); - } - - @Override - public void visit(CustomPropertyPathElement element) { - // noting to do for custom properties. - } - - @Override - public void visit(CustomPropertyArrayIndex element) { - // noting to do for custom properties. - } - - @Override - public void queryEntityType(EntityType type, Id targetId, TableRef lastRef) { - if (!(lastRef instanceof TableRefString)) { - throw new IllegalArgumentException("This implementation expect a TableRefString"); - } - TableRefString last = (TableRefString) lastRef; - - UUID id = null; - if (targetId != null) { - if (targetId.getBasicPersistenceType() != BasicPersistenceType.ByteArray) { - throw new IllegalArgumentException("This implementation expects UUID ids, not " + targetId.getBasicPersistenceType()); - } - id = (UUID) targetId.asBasicPersistenceType(); - } - - switch (type) { - case Datastream: - queryDatastreams(id, last); - break; - - case MultiDatastream: - queryMultiDatastreams(id, last); - break; - - case FeatureOfInterest: - queryFeatures(id, last); - break; - - case HistoricalLocation: - queryHistLocations(id, last); - break; - - case Location: - queryLocations(id, last); - break; - - case Observation: - queryObservations(id, last); - break; - - case ObservedProperty: - queryObsProperties(id, last); - break; - - case Sensor: - querySensors(id, last); - break; - - case Thing: - queryThings(id, last); - break; - - default: - LOGGER.error("Unknown entity type {}!?", type); - throw new IllegalStateException("Unknown entity type " + type); - } - if (mainTable == null && !last.isEmpty()) { - mainTable = new TableRefString(last); - } - - } - - @Override - public Map> expressionsForProperty(EntityProperty property, Path qPath, Map> target) { - return PropertyResolver.expressionsForProperty(property, qPath, target); - } - - private void queryDatastreams(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QDatastreams qDataStreams = new QDatastreams(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qDataStreams, selectedProperties)); - sqlQuery.from(qDataStreams); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.thingId.eq(qThings.id)); - needsDistinct = true; - break; - - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.id.eq(qObservations.datastreamId)); - break; - - case Sensor: - QSensors qSensors = (QSensors) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.sensorId.eq(qSensors.id)); - needsDistinct = true; - break; - - case ObservedProperty: - QObsProperties qObsProperties = (QObsProperties) last.qPath; - sqlQuery.innerJoin(qDataStreams).on(qDataStreams.obsPropertyId.eq(qObsProperties.id)); - needsDistinct = true; - break; - - case Datastream: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Datastreams.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Datastream; - last.qPath = qDataStreams; - last.idPath = qDataStreams.id; - } - if (entityId != null) { - sqlQuery.where(qDataStreams.id.eq(entityId)); - } - } - - private void queryMultiDatastreams(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QMultiDatastreams qMultiDataStreams = new QMultiDatastreams(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qMultiDataStreams, selectedProperties)); - sqlQuery.from(qMultiDataStreams); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.thingId.eq(qThings.id)); - needsDistinct = true; - break; - - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.id.eq(qObservations.multiDatastreamId)); - break; - - case Sensor: - QSensors qSensors = (QSensors) last.qPath; - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.sensorId.eq(qSensors.id)); - needsDistinct = true; - break; - - case ObservedProperty: - QObsProperties qObsProperties = (QObsProperties) last.qPath; - QMultiDatastreamsObsProperties qMdOp = new QMultiDatastreamsObsProperties(alias + "j1"); - sqlQuery.innerJoin(qMdOp).on(qObsProperties.id.eq(qMdOp.obsPropertyId)); - sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.id.eq(qMdOp.multiDatastreamId)); - if (!isFilter) { - sqlQuery.orderBy(qMdOp.rank.asc()); - } else { - needsDistinct = true; - } - break; - - case MultiDatastream: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Datastreams.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.MultiDatastream; - last.qPath = qMultiDataStreams; - last.idPath = qMultiDataStreams.id; - } - if (entityId != null) { - sqlQuery.where(qMultiDataStreams.id.eq(entityId)); - } - } - - private void queryThings(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QThings qThings = new QThings(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qThings, selectedProperties)); - sqlQuery.from(qThings); - } else { - switch (last.type) { - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qDatastreams.thingId)); - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qMultiDatastreams.thingId)); - break; - - case HistoricalLocation: - QHistLocations qHistLocations = (QHistLocations) last.qPath; - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qHistLocations.thingId)); - break; - - case Location: - QLocations qLocations = (QLocations) last.qPath; - QThingsLocations qTL = new QThingsLocations(alias + "j1"); - sqlQuery.innerJoin(qTL).on(qLocations.id.eq(qTL.locationId)); - sqlQuery.innerJoin(qThings).on(qThings.id.eq(qTL.thingId)); - needsDistinct = true; - break; - - case Thing: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Things.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Thing; - last.qPath = qThings; - last.idPath = qThings.id; - } - if (entityId != null) { - sqlQuery.where(qThings.id.eq(entityId)); - } - } - - private void queryFeatures(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QFeatures qFeatures = new QFeatures(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qFeatures, selectedProperties)); - sqlQuery.from(qFeatures); - } else { - switch (last.type) { - case Observation: - QObservations qObservations = (QObservations) last.qPath; - sqlQuery.innerJoin(qFeatures).on(qFeatures.id.eq(qObservations.featureId)); - break; - - case FeatureOfInterest: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Features.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.FeatureOfInterest; - last.qPath = qFeatures; - last.idPath = qFeatures.id; - } - if (entityId != null) { - sqlQuery.where(qFeatures.id.eq(entityId)); - } - } - - private void queryHistLocations(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QHistLocations qHistLocations = new QHistLocations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qHistLocations, selectedProperties)); - sqlQuery.from(qHistLocations); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - sqlQuery.innerJoin(qHistLocations).on(qThings.id.eq(qHistLocations.thingId)); - needsDistinct = true; - break; - - case Location: - QLocations qLocations = (QLocations) last.qPath; - QLocationsHistLocations qLHL = new QLocationsHistLocations(alias + "j1"); - sqlQuery.innerJoin(qLHL).on(qLocations.id.eq(qLHL.locationId)); - sqlQuery.innerJoin(qHistLocations).on(qHistLocations.id.eq(qLHL.histLocationId)); - needsDistinct = true; - break; - - case HistoricalLocation: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto HistLocations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.HistoricalLocation; - last.qPath = qHistLocations; - last.idPath = qHistLocations.id; - } - if (entityId != null) { - sqlQuery.where(qHistLocations.id.eq(entityId)); - } - } - - private void queryLocations(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QLocations qLocations = new QLocations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qLocations, selectedProperties)); - sqlQuery.from(qLocations); - } else { - switch (last.type) { - case Thing: - QThings qThings = (QThings) last.qPath; - QThingsLocations qTL = new QThingsLocations(alias + "j1"); - sqlQuery.innerJoin(qTL).on(qThings.id.eq(qTL.thingId)); - sqlQuery.innerJoin(qLocations).on(qLocations.id.eq(qTL.locationId)); - needsDistinct = true; - break; - - case HistoricalLocation: - QHistLocations qHistLocations = (QHistLocations) last.qPath; - QLocationsHistLocations qLHL = new QLocationsHistLocations(alias + "j1"); - sqlQuery.innerJoin(qLHL).on(qHistLocations.id.eq(qLHL.histLocationId)); - sqlQuery.innerJoin(qLocations).on(qLocations.id.eq(qLHL.locationId)); - needsDistinct = true; - break; - - case Location: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Locations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Location; - last.qPath = qLocations; - last.idPath = qLocations.id; - } - if (entityId != null) { - sqlQuery.where(qLocations.id.eq(entityId)); - } - } - - private void querySensors(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QSensors qSensors = new QSensors(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qSensors, selectedProperties)); - sqlQuery.from(qSensors); - } else { - switch (last.type) { - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qSensors).on(qSensors.id.eq(qDatastreams.sensorId)); - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qSensors).on(qSensors.id.eq(qMultiDatastreams.sensorId)); - break; - - case Sensor: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Sensors.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Sensor; - last.qPath = qSensors; - last.idPath = qSensors.id; - } - if (entityId != null) { - sqlQuery.where(qSensors.id.eq(entityId)); - } - } - - private void queryObservations(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QObservations qObservations = new QObservations(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qObservations, selectedProperties)); - sqlQuery.from(qObservations); - } else { - switch (last.type) { - case FeatureOfInterest: - QFeatures qFeatures = (QFeatures) last.qPath; - sqlQuery.innerJoin(qObservations).on(qFeatures.id.eq(qObservations.featureId)); - needsDistinct = true; - break; - - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qObservations).on(qDatastreams.id.eq(qObservations.datastreamId)); - needsDistinct = true; - break; - - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - sqlQuery.innerJoin(qObservations).on(qMultiDatastreams.id.eq(qObservations.multiDatastreamId)); - needsDistinct = true; - break; - - case Observation: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto Observations.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.Observation; - last.qPath = qObservations; - last.idPath = qObservations.id; - } - if (entityId != null) { - sqlQuery.where(qObservations.id.eq(entityId)); - } - } - - private void queryObsProperties(UUID entityId, TableRefString last) { - int nr = ++aliasNr; - String alias = ALIAS_PREFIX + nr; - QObsProperties qObsProperties = new QObsProperties(alias); - boolean added = true; - if (last.type == null) { - sqlQuery.select(PropertyHelper.getExpressions(qObsProperties, selectedProperties)); - sqlQuery.from(qObsProperties); - } else { - switch (last.type) { - case MultiDatastream: - QMultiDatastreams qMultiDatastreams = (QMultiDatastreams) last.qPath; - QMultiDatastreamsObsProperties qMdOp = new QMultiDatastreamsObsProperties(alias + "j1"); - sqlQuery.innerJoin(qMdOp).on(qMultiDatastreams.id.eq(qMdOp.multiDatastreamId)); - sqlQuery.innerJoin(qObsProperties).on(qObsProperties.id.eq(qMdOp.obsPropertyId)); - needsDistinct = true; - needsDistinct = true; - break; - - case Datastream: - QDatastreams qDatastreams = (QDatastreams) last.qPath; - sqlQuery.innerJoin(qObsProperties).on(qObsProperties.id.eq(qDatastreams.obsPropertyId)); - break; - case ObservedProperty: - added = false; - break; - - default: - LOGGER.error("Do not know how to join {} onto ObsProperties.", last.type); - throw new IllegalStateException("Do not know how to join"); - } - } - if (added) { - last.type = EntityType.ObservedProperty; - last.qPath = qObsProperties; - last.idPath = qObsProperties.id; - } - if (entityId != null) { - sqlQuery.where(qObsProperties.id.eq(entityId)); - } - } - -} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PostgresPersistenceManagerUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PostgresPersistenceManagerUuid.java index ae0bd7e0d..0cd07dc9c 100644 --- a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PostgresPersistenceManagerUuid.java +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PostgresPersistenceManagerUuid.java @@ -17,59 +17,29 @@ */ package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid; -import com.querydsl.core.Tuple; -import com.querydsl.sql.SQLExpressions; -import com.querydsl.sql.SQLQuery; -import com.querydsl.sql.SQLQueryFactory; -import com.querydsl.sql.SQLTemplates; -import com.querydsl.sql.dml.SQLDeleteClause; -import com.querydsl.sql.spatial.PostGISTemplates; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; +import com.querydsl.core.types.dsl.ComparablePath; import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; -import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; -import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; -import de.fraunhofer.iosb.ilt.sta.persistence.AbstractPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PathSqlBuilder; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.IdGenerationHandler; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; -import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PropertyResolver; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QDatastreamsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QFeaturesUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QHistLocationsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QLocationsHistLocationsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QLocationsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QMultiDatastreamsObsPropertiesUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QMultiDatastreamsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QObsPropertiesUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QObservationsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QSensorsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QThingsLocationsUuid; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths.QThingsUuid; import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; -import de.fraunhofer.iosb.ilt.sta.settings.Settings; -import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; -import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; -import java.io.StringWriter; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.List; -import java.util.Map; import java.util.UUID; -import javax.inject.Provider; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.sql.DataSource; -import liquibase.Contexts; -import liquibase.Liquibase; -import liquibase.database.Database; -import liquibase.database.DatabaseFactory; -import liquibase.database.jvm.JdbcConnection; -import liquibase.exception.DatabaseException; -import liquibase.exception.LiquibaseException; -import liquibase.resource.ClassLoaderResourceAccessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * @@ -77,462 +47,66 @@ * @author scf * @author selimnairb */ -public class PostgresPersistenceManagerUuid extends AbstractPersistenceManager implements PostgresPersistenceManager { +public class PostgresPersistenceManagerUuid extends PostgresPersistenceManager, UUID> { private static final String LIQUIBASE_CHANGELOG_FILENAME = "liquibase/tablesUuid.xml"; - private static final Logger LOGGER = LoggerFactory.getLogger(PostgresPersistenceManagerUuid.class); - private static final IdManager UUID_ID_MANAGER = new IdManager() { - @Override - public Class getIdClass() { - return UuidId.class; - } - - @Override - public UuidId parseId(String input) { - if (input.startsWith("'")) { - return new UuidId(input.substring(1, input.length() - 1)); - } - return new UuidId(input); - } - }; - - private static class MyConnectionWrapper implements Provider { - - private final CoreSettings settings; - private Connection connection; - - public MyConnectionWrapper(CoreSettings settings) { - this.settings = settings; - } - - @Override - public Connection get() { - if (connection == null) { - try { - connection = getConnection(settings); - } catch (NamingException | SQLException ex) { - LOGGER.error("Could not inizialize " + getClass().getName(), ex); - } - } - return connection; - } - - public void clear() { - connection = null; - } - - } - - private MyConnectionWrapper connectionProvider; - private SQLQueryFactory queryFactory; - private CoreSettings settings; - - public PostgresPersistenceManagerUuid() { - - } + private static final IdManager ID_MANAGER = new IdManagerUuid(); + private static EntityFactories, UUID> entityFactories; + private static PropertyResolver, UUID> propertyResolver; @Override public IdManager getIdManager() { - return UUID_ID_MANAGER; + return ID_MANAGER; } @Override public void init(CoreSettings settings) { - this.settings = settings; - connectionProvider = new MyConnectionWrapper(settings); - } - - @Override - public CoreSettings getCoreSettings() { - return settings; - } - - @Override - public boolean doInsert(Entity entity) throws NoSuchEntityException, IncompleteEntityException { - EntityInserter ei = new EntityInserter(this); - switch (entity.getEntityType()) { - case Datastream: - ei.insertDatastream((Datastream) entity); - break; - - case MultiDatastream: - ei.insertMultiDatastream((MultiDatastream) entity); - break; - - case FeatureOfInterest: - ei.insertFeatureOfInterest((FeatureOfInterest) entity); - break; - - case HistoricalLocation: - ei.insertHistoricalLocation((HistoricalLocation) entity); - break; - - case Location: - ei.insertLocation((Location) entity); - break; - - case Observation: - ei.insertObservation((Observation) entity); - break; - - case ObservedProperty: - ei.insertObservedProperty((ObservedProperty) entity); - break; - - case Sensor: - ei.insertSensor((Sensor) entity); - break; - - case Thing: - ei.insertThing((Thing) entity); - break; - - default: - throw new IllegalStateException("Unknown entity type: " + entity.getEntityType().name()); - + super.init(settings); + IdGenerationHandlerUuid.setIdGenerationMode(settings.getPersistenceSettings().getIdGenerationMode()); + if (entityFactories == null) { + QCollection qCollection = new QCollection( + QDatastreamsUuid.DATASTREAMS, + QFeaturesUuid.FEATURES, + QHistLocationsUuid.HISTLOCATIONS, + QLocationsUuid.LOCATIONS, + QMultiDatastreamsUuid.MULTIDATASTREAMS, + QObsPropertiesUuid.OBSPROPERTIES, + QObservationsUuid.OBSERVATIONS, + QSensorsUuid.SENSORS, + QThingsUuid.THINGS, + QLocationsHistLocationsUuid.LOCATIONSHISTLOCATIONS, + QMultiDatastreamsObsPropertiesUuid.MULTIDATASTREAMSOBSPROPERTIES, + QThingsLocationsUuid.THINGSLOCATIONS); + init(qCollection); } - return true; } - @Override - public boolean doDelete(EntityPathElement pathElement) throws NoSuchEntityException { - SQLQueryFactory qf = createQueryFactory(); - UUID id = (UUID) pathElement.getId().getValue(); - SQLDeleteClause delete; - EntityType type = pathElement.getEntityType(); - switch (type) { - case Datastream: - delete = qf.delete(QDatastreams.datastreams).where(QDatastreams.datastreams.id.eq(id)); - break; - - case MultiDatastream: - delete = qf.delete(QMultiDatastreams.multiDatastreams).where(QMultiDatastreams.multiDatastreams.id.eq(id)); - break; - - case FeatureOfInterest: - delete = qf.delete(QFeatures.features).where(QFeatures.features.id.eq(id)); - break; - - case HistoricalLocation: - delete = qf.delete(QHistLocations.histLocations).where(QHistLocations.histLocations.id.eq(id)); - break; - - case Location: { - delete = qf.delete(QLocations.locations).where(QLocations.locations.id.eq(id)); - long count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} Locations", count); - - // Also delete all historicalLocations that no longer reference any location - QHistLocations qhl = QHistLocations.histLocations; - QLocationsHistLocations qlhl = QLocationsHistLocations.locationsHistLocations; - delete = qf.delete(qhl) - .where(qhl.id.in( - SQLExpressions.select(qhl.id) - .from(qhl) - .leftJoin(qlhl).on(qhl.id.eq(qlhl.histLocationId)) - .where(qlhl.locationId.isNull()) - )); - count = delete.execute(); - LOGGER.debug("Deleted {} HistoricalLocations", count); - return true; - } - - case Observation: - delete = qf.delete(QObservations.observations).where(QObservations.observations.id.eq(id)); - break; - - case ObservedProperty: { - // First delete all MultiDatastreams that link to this ObservedProperty. - QMultiDatastreams qMd = QMultiDatastreams.multiDatastreams; - QMultiDatastreamsObsProperties qMdOp = QMultiDatastreamsObsProperties.multiDatastreamsObsProperties; - delete = qf.delete(qMd).where(qMd.id.in( - SQLExpressions.select(qMdOp.multiDatastreamId).from(qMdOp).where(qMdOp.obsPropertyId.eq(id)) - )); - long count = delete.execute(); - LOGGER.debug("Deleted {} MultiDatastreams.", count); - - delete = qf.delete(QObsProperties.obsProperties).where(QObsProperties.obsProperties.id.eq(id)); - count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} ObservedProperties", count); - return true; - } - - case Sensor: - delete = qf.delete(QSensors.sensors).where(QSensors.sensors.id.eq(id)); - break; - - case Thing: - delete = qf.delete(QThings.things).where(QThings.things.id.eq(id)); - break; - - default: - throw new NoSuchEntityException("Unknown entity type: " + pathElement.getEntityType()); - } - if (delete != null) { - long count = delete.execute(); - if (count == 0) { - throw new NoSuchEntityException("No " + type + " with id " + id); - } - LOGGER.debug("Deleted {} entries of type {}", count, type); + private static synchronized void init(QCollection qCollection) { + if (entityFactories == null) { + entityFactories = new EntityFactories(ID_MANAGER, qCollection); + propertyResolver = new PropertyResolver<>(entityFactories, BasicPersistenceType.BYTEARRAY); } - return true; } @Override - public boolean doUpdate(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException { - EntityInserter ei = new EntityInserter(this); - entity.setId(pathElement.getId()); - UUID id = (UUID) pathElement.getId().getValue(); - if (!ei.entityExists(entity)) { - throw new NoSuchEntityException("No entity of type " + pathElement.getEntityType() + " with id " + id); - } - EntityType type = pathElement.getEntityType(); - switch (type) { - case Datastream: - ei.updateDatastream((Datastream) entity, id); - break; - - case MultiDatastream: - ei.updateMultiDatastream((MultiDatastream) entity, id); - break; - - case FeatureOfInterest: - ei.updateFeatureOfInterest((FeatureOfInterest) entity, id); - break; - - case HistoricalLocation: - ei.updateHistoricalLocation((HistoricalLocation) entity, id); - break; - - case Location: - ei.updateLocation((Location) entity, id); - break; - - case Observation: - ei.updateObservation((Observation) entity, id); - break; - - case ObservedProperty: - ei.updateObservedProperty((ObservedProperty) entity, id); - break; - - case Sensor: - ei.updateSensor((Sensor) entity, id); - break; - - case Thing: - ei.updateThing((Thing) entity, id); - break; - - default: - throw new AssertionError(type.name()); - - } - - return true; + public PropertyResolver, UUID> getPropertyResolver() { + return propertyResolver; } @Override - protected boolean doCommit() { - try { - if (!connectionProvider.get().isClosed()) { - connectionProvider.get().commit(); - return true; - } - } catch (SQLException ex) { - LOGGER.error("Exception rolling back.", ex); - } - return false; - } - - @Override - protected boolean doRollback() { - try { - if (!connectionProvider.get().isClosed()) { - LOGGER.info("Rolling back changes."); - connectionProvider.get().rollback(); - return true; - } - } catch (SQLException ex) { - LOGGER.error("Exception rolling back.", ex); - } - return false; + public String getLiquibaseChangelogFilename() { + return LIQUIBASE_CHANGELOG_FILENAME; } @Override - protected boolean doClose() { - try { - connectionProvider.get().close(); - return true; - } catch (SQLException ex) { - LOGGER.error("Exception closing.", ex); - } finally { - connectionProvider.clear(); - } - return false; - } - - public static Connection getConnection(CoreSettings settings) throws NamingException, SQLException { - Settings customSettings = settings.getPersistenceSettings().getCustomSettings(); - if (customSettings.contains(TAG_DATA_SOURCE)) { - String dataSourceName = customSettings.getString(TAG_DATA_SOURCE); - if (dataSourceName != null && !dataSourceName.isEmpty()) { - InitialContext cxt = new InitialContext(); - if (cxt == null) { - throw new IllegalStateException("No context!"); - } - - DataSource ds = (DataSource) cxt.lookup("java:/comp/env/" + dataSourceName); - if (ds == null) { - throw new IllegalStateException("Data source not found!"); - } - Connection connection = ds.getConnection(); - connection.setAutoCommit(false); - return connection; - } - } - if (!customSettings.contains(TAG_DB_DRIVER) || customSettings.getString(TAG_DB_DRIVER).isEmpty()) { - throw new IllegalArgumentException("Property '" + TAG_DB_DRIVER + "' must be non-empty"); - } - try { - Class.forName(customSettings.getString(TAG_DB_DRIVER)); - } catch (ClassNotFoundException ex) { - LOGGER.error("Could not initialise database.", ex); - throw new IllegalArgumentException(ex); - } - - Connection connection = DriverManager.getConnection( - customSettings.getString(TAG_DB_URL), - customSettings.getString(TAG_DB_USERNAME), - customSettings.getString(TAG_DB_PASSWORD)); - - connection.setAutoCommit(false); - return connection; - } - - public SQLQueryFactory createQueryFactory() { - if (queryFactory == null) { - SQLTemplates templates = PostGISTemplates.builder().quote().build(); - queryFactory = new SQLQueryFactory(templates, connectionProvider); - } - return queryFactory; + public EntityFactories, UUID> getEntityFactories() { + return entityFactories; } @Override - public boolean validatePath(ResourcePath path) { - ResourcePathElement element = path.getIdentifiedElement(); - if (element == null) { - return true; - } - ResourcePath tempPath = new ResourcePath(); - List elements = tempPath.getPathElements(); - while (element != null) { - elements.add(0, element); - element = element.getParent(); - } - return new EntityInserter(this).entityExists(tempPath); - } - - @Override - public Object get(ResourcePath path, Query query) { - ResourcePathElement lastElement = path.getLastElement(); - if (!(lastElement instanceof EntityPathElement) && !(lastElement instanceof EntitySetPathElement)) { - if (!query.getExpand().isEmpty()) { - LOGGER.warn("Expand only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); - query.getExpand().clear(); - } - if (!query.getSelect().isEmpty()) { - LOGGER.warn("Select only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); - query.getSelect().clear(); - } - } - - SQLQueryFactory qf = createQueryFactory(); - PathSqlBuilder psb = new PathSqlBuilderUuid(); - SQLQuery sqlQuery = psb.buildFor(path, query, qf, settings.getPersistenceSettings()); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Generated SQL:\n{}", sqlQuery.getSQL().getSQL()); - } - - EntityCreator entityCreator = new EntityCreator(this, path, query, sqlQuery); - lastElement.visit(entityCreator); - Object entity = entityCreator.getEntity(); - - if (path.isValue() && entity instanceof Map) { - Map map = (Map) entity; - entity = map.get(entityCreator.getEntityName()); - } - - return entity; - } - - public long count(ResourcePath path, Query query) { - SQLQueryFactory qf = createQueryFactory(); - PathSqlBuilder psb = new PathSqlBuilderUuid(); - SQLQuery sqlQuery = psb.buildFor(path, query, qf, settings.getPersistenceSettings()); - return sqlQuery.fetchCount(); - } - - @Override - public String checkForUpgrades() { - StringWriter out = new StringWriter(); - try { - Connection connection = getConnection(settings); - - Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); - Liquibase liquibase = new liquibase.Liquibase(LIQUIBASE_CHANGELOG_FILENAME, new ClassLoaderResourceAccessor(), database); - liquibase.update(new Contexts(), out); - database.commit(); - database.close(); - connection.close(); - - } catch (SQLException | DatabaseException | NamingException ex) { - LOGGER.error("Could not initialise database.", ex); - out.append("Failed to initialise database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } catch (LiquibaseException ex) { - LOGGER.error("Could not upgrade database.", ex); - out.append("Failed to upgrade database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } - return out.toString(); - } - - @Override - public String doUpgrades() { - StringWriter out = new StringWriter(); - try { - Connection connection = getConnection(settings); - - Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); - Liquibase liquibase = new liquibase.Liquibase(LIQUIBASE_CHANGELOG_FILENAME, new ClassLoaderResourceAccessor(), database); - liquibase.update(new Contexts()); - database.commit(); - database.close(); - connection.close(); - - } catch (SQLException | DatabaseException | NamingException ex) { - LOGGER.error("Could not initialise database.", ex); - out.append("Failed to initialise database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } catch (LiquibaseException ex) { - LOGGER.error("Could not upgrade database.", ex); - out.append("Failed to upgrade database:\n"); - out.append(ex.getLocalizedMessage()); - out.append("\n"); - } - return out.toString(); + public IdGenerationHandler createIdGenerationHanlder(Entity e) { + return new IdGenerationHandlerUuid(e); } } diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PropertyHelper.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PropertyHelper.java deleted file mode 100644 index 3a780a7b5..000000000 --- a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PropertyHelper.java +++ /dev/null @@ -1,832 +0,0 @@ -/* - * Copyright (C) 2016 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 - * aString with this program. If not, see . - */ -package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.mysema.commons.lang.CloseableIterator; -import com.querydsl.core.Tuple; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import com.querydsl.core.types.dsl.ComparablePath; -import de.fraunhofer.iosb.ilt.sta.deserialize.custom.geojson.GeoJsonDeserializier; -import de.fraunhofer.iosb.ilt.sta.model.Datastream; -import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; -import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; -import de.fraunhofer.iosb.ilt.sta.model.Location; -import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; -import de.fraunhofer.iosb.ilt.sta.model.Observation; -import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; -import de.fraunhofer.iosb.ilt.sta.model.Sensor; -import de.fraunhofer.iosb.ilt.sta.model.Thing; -import de.fraunhofer.iosb.ilt.sta.model.core.Entity; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; -import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; -import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.EntityType; -import de.fraunhofer.iosb.ilt.sta.path.Property; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.ResultType; -import de.fraunhofer.iosb.ilt.sta.query.Query; -import de.fraunhofer.iosb.ilt.sta.util.GeoHelper; -import java.io.IOException; -import java.math.BigDecimal; -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import org.geojson.Polygon; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author scf - * @author selimnairb - */ -public class PropertyHelper { - - public static interface entityFromTupleFactory { - - /** - * Creates a T, reading the Tuple with a qObject using no alias. - * - * @param tuple The tuple to create the Entity from. - * @param query The query used to request the data. - * @param dataSize The counter for the data size. This counts only the - * variable-sided elements, such as Observation.result and - * Thing.properties. - * @return The Entity created from the Tuple. - */ - public T create(Tuple tuple, Query query, DataSize dataSize); - - /** - * Get the primary key of the table of the entity this factory - * - * @return The primary key of the table of the entity this factory - * creates, using no alias. - */ - public ComparablePath getPrimaryKey(); - - /** - * Get the EntityType of the Entities created by this factory. - * - * @return The EntityType of the Entities created by this factory. - */ - public EntityType getEntityType(); - - } - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PropertyHelper.class); - private static final TypeReference> TYPE_LIST_STRING = new TypeReference>() { - // Empty on purpose. - }; - private static final TypeReference> TYPE_LIST_UOM = new TypeReference>() { - // Empty on purpose. - }; - private static final Map, entityFromTupleFactory> FACTORY_PER_ENTITY = new HashMap<>(); - - public static Expression[] getExpressions(Path qPath, Set selectedProperties) { - List> exprList = new ArrayList<>(); - if (selectedProperties.isEmpty()) { - PropertyResolver.expressionsForClass(qPath, exprList); - } else { - for (EntityProperty property : selectedProperties) { - PropertyResolver.expressionsForProperty(property, qPath, exprList); - } - } - return exprList.toArray(new Expression[exprList.size()]); - } - - public static EntitySet createSetFromTuples(entityFromTupleFactory factory, CloseableIterator tuples, Query query, long maxDataSize) { - EntitySet entitySet = new EntitySetImpl<>(factory.getEntityType()); - int count = 0; - DataSize size = new DataSize(); - int top = query.getTopOrDefault(); - while (tuples.hasNext()) { - Tuple tuple = tuples.next(); - entitySet.add(factory.create(tuple, query, size)); - count++; - if (count >= top) { - return entitySet; - } - if (size.getDataSize() > maxDataSize) { - LOGGER.debug("Size limit reached: {} > {}.", size.getDataSize(), maxDataSize); - return entitySet; - } - } - return entitySet; - } - - public static class DatastreamFactory implements PropertyHelper.entityFromTupleFactory { - - public static final DatastreamFactory withDefaultAlias = new DatastreamFactory(new QDatastreams(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QDatastreams qInstance; - - public DatastreamFactory(QDatastreams qInstance) { - this.qInstance = qInstance; - } - - @Override - public Datastream create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Datastream entity = new Datastream(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - entity.setObservationType(tuple.get(qInstance.observationType)); - - String observedArea = tuple.get(qInstance.observedArea.asText()); - if (observedArea != null) { - try { - Polygon polygon = GeoHelper.parsePolygon(observedArea); - entity.setObservedArea(polygon); - } catch (IllegalArgumentException e) { - // It's not a polygon, probably a point or a line. - } - } - ObservedProperty op = observedProperyFromId(tuple.get(qInstance.obsPropertyId)); - entity.setObservedProperty(op); - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - if (pTimeStart != null && pTimeEnd != null) { - entity.setPhenomenonTime(intervalFromTimes(pTimeStart, pTimeEnd)); - } - - Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); - Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); - if (rTimeStart != null && rTimeEnd != null) { - entity.setResultTime(intervalFromTimes(rTimeStart, rTimeEnd)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - entity.setSensor(sensorFromId(tuple.get(qInstance.sensorId))); - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - - entity.setUnitOfMeasurement(new UnitOfMeasurement(tuple.get(qInstance.unitName), tuple.get(qInstance.unitSymbol), tuple.get(qInstance.unitDefinition))); - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Datastream; - } - - } - - public static class MultiDatastreamFactory implements PropertyHelper.entityFromTupleFactory { - - public static final MultiDatastreamFactory withDefaultAlias = new MultiDatastreamFactory(new QMultiDatastreams(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QMultiDatastreams qInstance; - - public MultiDatastreamFactory(QMultiDatastreams qInstance) { - this.qInstance = qInstance; - } - - @Override - public MultiDatastream create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - MultiDatastream entity = new MultiDatastream(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - List observationTypes = jsonToObject(tuple.get(qInstance.observationTypes), TYPE_LIST_STRING); - entity.setMultiObservationDataTypes(observationTypes); - - String observedArea = tuple.get(qInstance.observedArea.asText()); - if (observedArea != null) { - try { - Polygon polygon = GeoHelper.parsePolygon(observedArea); - entity.setObservedArea(polygon); - } catch (IllegalArgumentException e) { - // It's not a polygon, probably a point or a line. - } - } - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - if (pTimeStart != null && pTimeEnd != null) { - entity.setPhenomenonTime(intervalFromTimes(pTimeStart, pTimeEnd)); - } - - Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); - Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); - if (rTimeStart != null && rTimeEnd != null) { - entity.setResultTime(intervalFromTimes(rTimeStart, rTimeEnd)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - entity.setSensor(sensorFromId(tuple.get(qInstance.sensorId))); - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - - List units = jsonToObject(tuple.get(qInstance.unitOfMeasurements), TYPE_LIST_UOM); - entity.setUnitOfMeasurements(units); - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.MultiDatastream; - } - - } - - public static class ThingFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ThingFactory withDefaultAlias = new ThingFactory(new QThings(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QThings qInstance; - - public ThingFactory(QThings qInstance) { - this.qInstance = qInstance; - } - - @Override - public Thing create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Thing entity = new Thing(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - dataSize.increase(props == null ? 0 : props.length()); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Thing; - } - - } - - public static class FeatureOfInterestFactory implements PropertyHelper.entityFromTupleFactory { - - public static final FeatureOfInterestFactory withDefaultAlias = new FeatureOfInterestFactory(new QFeatures(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QFeatures qInstance; - - public FeatureOfInterestFactory(QFeatures qInstance) { - this.qInstance = qInstance; - } - - @Override - public FeatureOfInterest create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - FeatureOfInterest entity = new FeatureOfInterest(); - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String encodingType = tuple.get(qInstance.encodingType); - entity.setEncodingType(encodingType); - - if (select.isEmpty() || select.contains(EntityProperty.Feature)) { - String locationString = tuple.get(qInstance.feature); - dataSize.increase(locationString == null ? 0 : locationString.length()); - entity.setFeature(locationFromEncoding(encodingType, locationString)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.FeatureOfInterest; - } - - } - - public static class HistoricalLocationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final HistoricalLocationFactory withDefaultAlias = new HistoricalLocationFactory(new QHistLocations(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QHistLocations qInstance; - - public HistoricalLocationFactory(QHistLocations qInstance) { - this.qInstance = qInstance; - } - - @Override - public HistoricalLocation create(Tuple tuple, Query query, DataSize dataSize) { - HistoricalLocation entity = new HistoricalLocation(); - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - entity.setThing(thingFromId(tuple.get(qInstance.thingId))); - entity.setTime(instantFromTime(tuple.get(qInstance.time))); - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.HistoricalLocation; - } - - } - - public static class LocationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final LocationFactory withDefaultAlias = new LocationFactory(new QLocations(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QLocations qInstance; - - public LocationFactory(QLocations qInstance) { - this.qInstance = qInstance; - } - - @Override - public Location create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - Location entity = new Location(); - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - String encodingType = tuple.get(qInstance.encodingType); - entity.setEncodingType(encodingType); - - if (select.isEmpty() || select.contains(EntityProperty.Location)) { - String locationString = tuple.get(qInstance.location); - dataSize.increase(locationString == null ? 0 : locationString.length()); - entity.setLocation(locationFromEncoding(encodingType, locationString)); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Location; - } - - } - - public static class SensorFactory implements PropertyHelper.entityFromTupleFactory { - - public static final SensorFactory withDefaultAlias = new SensorFactory(new QSensors(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QSensors qInstance; - - public SensorFactory(QSensors qInstance) { - this.qInstance = qInstance; - } - - @Override - public Sensor create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - Sensor entity = new Sensor(); - entity.setName(tuple.get(qInstance.name)); - entity.setDescription(tuple.get(qInstance.description)); - entity.setEncodingType(tuple.get(qInstance.encodingType)); - - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Metadata)) { - String metaDataString = tuple.get(qInstance.metadata); - dataSize.increase(metaDataString == null ? 0 : metaDataString.length()); - entity.setMetadata(metaDataString); - } - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Sensor; - } - - } - - public static class ObservationFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ObservationFactory withDefaultAlias = new ObservationFactory(new QObservations(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QObservations qInstance; - - public ObservationFactory(QObservations qInstance) { - this.qInstance = qInstance; - } - - @Override - public Observation create(Tuple tuple, Query query, DataSize dataSize) { - Observation entity = new Observation(); - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - UUID dsId = tuple.get(qInstance.datastreamId); - if (dsId != null) { - entity.setDatastream(datastreamFromId(dsId)); - } - UUID mDsId = tuple.get(qInstance.multiDatastreamId); - if (mDsId != null) { - entity.setMultiDatastream(multiDatastreamFromId(mDsId)); - } - - entity.setFeatureOfInterest(featureOfInterestFromId(tuple.get(qInstance.featureId))); - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - if (select.isEmpty() || select.contains(EntityProperty.Parameters)) { - String props = tuple.get(qInstance.parameters); - dataSize.increase(props == null ? 0 : props.length()); - entity.setParameters(jsonToObject(props, Map.class)); - } - - Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); - Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); - entity.setPhenomenonTime(valueFromTimes(pTimeStart, pTimeEnd)); - - if (select.isEmpty() || select.contains(EntityProperty.Result)) { - Byte resultTypeOrd = tuple.get(qInstance.resultType); - if (resultTypeOrd != null) { - ResultType resultType = ResultType.fromSqlValue(resultTypeOrd); - switch (resultType) { - case BOOLEAN: - entity.setResult(tuple.get(qInstance.resultBoolean)); - break; - - case NUMBER: - try { - entity.setResult(new BigDecimal(tuple.get(qInstance.resultString))); - } catch (NumberFormatException e) { - // It was not a Number? Use the double value. - entity.setResult(tuple.get(qInstance.resultNumber)); - } - break; - - case OBJECT_ARRAY: - String jsonData = tuple.get(qInstance.resultJson); - dataSize.increase(jsonData == null ? 0 : jsonData.length()); - entity.setResult(jsonToTree(jsonData)); - break; - - case STRING: - String stringData = tuple.get(qInstance.resultString); - dataSize.increase(stringData == null ? 0 : stringData.length()); - entity.setResult(stringData); - break; - } - } - } - - if (select.isEmpty() || select.contains(EntityProperty.ResultQuality)) { - String resultQuality = tuple.get(qInstance.resultQuality); - dataSize.increase(resultQuality == null ? 0 : resultQuality.length()); - entity.setResultQuality(jsonToObject(resultQuality, Object.class)); - } - - entity.setResultTime(instantFromTime(tuple.get(qInstance.resultTime))); - - Timestamp vTimeStart = tuple.get(qInstance.validTimeStart); - Timestamp vTimeEnd = tuple.get(qInstance.validTimeEnd); - if (vTimeStart != null && vTimeEnd != null) { - entity.setValidTime(intervalFromTimes(vTimeStart, vTimeEnd)); - } - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.Observation; - } - - } - - public static class ObservedPropertyFactory implements PropertyHelper.entityFromTupleFactory { - - public static final ObservedPropertyFactory withDefaultAlias = new ObservedPropertyFactory(new QObsProperties(PathSqlBuilderUuid.ALIAS_PREFIX + "1")); - private final QObsProperties qInstance; - - public ObservedPropertyFactory(QObsProperties qInstance) { - this.qInstance = qInstance; - } - - @Override - public ObservedProperty create(Tuple tuple, Query query, DataSize dataSize) { - Set select = query == null ? Collections.emptySet() : query.getSelect(); - - ObservedProperty entity = new ObservedProperty(); - entity.setDefinition(tuple.get(qInstance.definition)); - entity.setDescription(tuple.get(qInstance.description)); - UUID id = tuple.get(qInstance.id); - if (id != null) { - entity.setId(new UuidId(tuple.get(qInstance.id))); - } - - entity.setName(tuple.get(qInstance.name)); - - if (select.isEmpty() || select.contains(EntityProperty.Properties)) { - String props = tuple.get(qInstance.properties); - entity.setProperties(jsonToObject(props, Map.class)); - } - - return entity; - } - - @Override - public ComparablePath getPrimaryKey() { - return qInstance.id; - } - - @Override - public EntityType getEntityType() { - return EntityType.ObservedProperty; - } - - } - - static { - FACTORY_PER_ENTITY.put(Datastream.class, DatastreamFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(MultiDatastream.class, MultiDatastreamFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Thing.class, ThingFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(FeatureOfInterest.class, FeatureOfInterestFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(HistoricalLocation.class, HistoricalLocationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Location.class, LocationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Sensor.class, SensorFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(Observation.class, ObservationFactory.withDefaultAlias); - FACTORY_PER_ENTITY.put(ObservedProperty.class, ObservedPropertyFactory.withDefaultAlias); - } - - /** - * Get the factory for the given entity class, using the default alias - * PathSqlBuilderUuid.ALIAS_PREFIX + "1". - * - * @param The type of entity to get the factory for. - * @param clazz The class of the entity to get the factory for. - * @return the factory for the given entity class. - */ - public static entityFromTupleFactory getFactoryFor(Class clazz) { - entityFromTupleFactory factory = FACTORY_PER_ENTITY.get(clazz); - if (factory == null) { - throw new AssertionError("No factory found for " + clazz.getName()); - } - return (entityFromTupleFactory) factory; - } - - private static TimeInterval intervalFromTimes(Timestamp timeStart, Timestamp timeEnd) { - if (timeStart == null) { - timeStart = Timestamp.valueOf(LocalDateTime.MAX); - } - if (timeEnd == null) { - timeEnd = Timestamp.valueOf(LocalDateTime.MIN); - } - if (timeEnd.before(timeStart)) { - return null; - } else { - return TimeInterval.create(timeStart.getTime(), timeEnd.getTime()); - } - } - - private static TimeInstant instantFromTime(Timestamp time) { - if (time == null) { - return new TimeInstant(null); - } - return TimeInstant.create(time.getTime()); - } - - private static TimeValue valueFromTimes(Timestamp timeStart, Timestamp timeEnd) { - if (timeEnd == null || timeEnd.equals(timeStart)) { - return instantFromTime(timeStart); - } - return intervalFromTimes(timeStart, timeEnd); - } - - private static Datastream datastreamFromId(UUID id) { - if (id == null) { - return null; - } - Datastream ds = new Datastream(); - ds.setId(new UuidId(id)); - ds.setExportObject(false); - return ds; - } - - private static MultiDatastream multiDatastreamFromId(UUID id) { - if (id == null) { - return null; - } - MultiDatastream ds = new MultiDatastream(); - ds.setId(new UuidId(id)); - ds.setExportObject(false); - return ds; - } - - private static FeatureOfInterest featureOfInterestFromId(UUID id) { - if (id == null) { - return null; - } - FeatureOfInterest foi = new FeatureOfInterest(); - foi.setId(new UuidId(id)); - foi.setExportObject(false); - return foi; - } - - private static ObservedProperty observedProperyFromId(UUID id) { - if (id == null) { - return null; - } - ObservedProperty op = new ObservedProperty(); - op.setId(new UuidId(id)); - op.setExportObject(false); - return op; - } - - private static Sensor sensorFromId(UUID id) { - if (id == null) { - return null; - } - Sensor sensor = new Sensor(); - sensor.setId(new UuidId(id)); - sensor.setExportObject(false); - return sensor; - } - - private static Thing thingFromId(UUID id) { - if (id == null) { - return null; - } - Thing thing = new Thing(); - thing.setId(new UuidId(id)); - thing.setExportObject(false); - return thing; - } - - public static Object locationFromEncoding(String encodingType, String locationString) { - if (locationString == null || locationString.isEmpty()) { - return null; - } - if (encodingType != null && GeoJsonDeserializier.encodings.contains(encodingType.toLowerCase())) { - try { - Object geoJson = new GeoJsonDeserializier().deserialize(locationString); - return geoJson; - } catch (IOException ex) { - LOGGER.error("Failed to deserialise geoJson."); - - } - } else { - try { - Map map = jsonToObject(locationString, Map.class - ); - return map; - } catch (Exception e) { - LOGGER.trace("Not a map."); - } - return locationString; - } - return null; - } - - public static JsonNode jsonToTree(String json) { - if (json == null) { - return null; - } - - try { - return new ObjectMapper().readTree(json); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - - public static T jsonToObject(String json, Class clazz) { - if (json == null) { - return null; - } - try { - return new ObjectMapper().readValue(json, clazz); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - - public static T jsonToObject(String json, TypeReference typeReference) { - if (json == null) { - return null; - } - try { - return new ObjectMapper().readValue(json, typeReference); - } catch (IOException ex) { - throw new IllegalStateException("Failed to parse stored json.", ex); - } - } - -} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PropertyResolver.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PropertyResolver.java deleted file mode 100644 index 1d393263d..000000000 --- a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/PropertyResolver.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.uuidid; - -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Path; -import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; -import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; -import de.fraunhofer.iosb.ilt.sta.path.Property; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_END; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_START; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author scf - */ -public class PropertyResolver { - - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PropertyResolver.class); - - private static interface ExpressionFactory { - - Expression get(T qPath); - } - - private static final Map> epMapSingle = new HashMap<>(); - private static final Map>> epMapMulti = new HashMap<>(); - private static final Map> allForClass = new HashMap<>(); - - static { - addEntry(EntityProperty.Id, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.Name, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.name); - addEntry(EntityProperty.Description, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.description); - addEntry(EntityProperty.ObservationType, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.observationType); - addEntry(EntityProperty.ObservedArea, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.observedArea.asText()); - addEntry(EntityProperty.PhenomenonTime, QDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QDatastreams qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QDatastreams qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Properties, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.properties); - addEntry(EntityProperty.ResultTime, QDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QDatastreams qPath) -> qPath.resultTimeStart); - addEntry(EntityProperty.ResultTime, QDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QDatastreams qPath) -> qPath.resultTimeEnd); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "definition", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitDefinition); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "name", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitName); - addEntry(EntityProperty.UnitOfMeasurement, QDatastreams.class, "symbol", (ExpressionFactory) (QDatastreams qPath) -> qPath.unitSymbol); - addEntry(NavigationProperty.Sensor, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.sensorId); - addEntry(NavigationProperty.ObservedProperty, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.obsPropertyId); - addEntry(NavigationProperty.Thing, QDatastreams.class, (ExpressionFactory) (QDatastreams qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.id); - addEntry(EntityProperty.Name, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.name); - addEntry(EntityProperty.Description, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.description); - addEntry(EntityProperty.MultiObservationDataTypes, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.observationTypes); - addEntry(EntityProperty.ObservedArea, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.observedArea.asText()); - addEntry(EntityProperty.PhenomenonTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Properties, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.properties); - addEntry(EntityProperty.ResultTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.resultTimeStart); - addEntry(EntityProperty.ResultTime, QMultiDatastreams.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.resultTimeEnd); - addEntry(EntityProperty.UnitOfMeasurements, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.unitOfMeasurements); - addEntry(NavigationProperty.Sensor, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.sensorId); - addEntry(NavigationProperty.Thing, QMultiDatastreams.class, (ExpressionFactory) (QMultiDatastreams qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.id); - addEntry(EntityProperty.Name, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.name); - addEntry(EntityProperty.Description, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.encodingType); - addEntry(EntityProperty.Feature, QFeatures.class, "j", (ExpressionFactory) (QFeatures qPath) -> qPath.feature); - addEntry(EntityProperty.Feature, QFeatures.class, "g", (ExpressionFactory) (QFeatures qPath) -> qPath.geom); - addEntry(EntityProperty.Properties, QFeatures.class, (ExpressionFactory) (QFeatures qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.id); - addEntry(EntityProperty.Time, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.time); - addEntry(NavigationProperty.Thing, QHistLocations.class, (ExpressionFactory) (QHistLocations qPath) -> qPath.thingId); - - addEntry(EntityProperty.Id, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.id); - addEntry(EntityProperty.Name, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.name); - addEntry(EntityProperty.Description, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.encodingType); - addEntry(EntityProperty.Location, QLocations.class, "j", (ExpressionFactory) (QLocations qPath) -> qPath.location); - addEntry(EntityProperty.Location, QLocations.class, "g", (ExpressionFactory) (QLocations qPath) -> qPath.geom); - addEntry(EntityProperty.Properties, QLocations.class, (ExpressionFactory) (QLocations qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.id); - addEntry(EntityProperty.Definition, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.definition); - addEntry(EntityProperty.Description, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.description); - addEntry(EntityProperty.Name, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.name); - addEntry(EntityProperty.Properties, QObsProperties.class, (ExpressionFactory) (QObsProperties qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.id); - addEntry(EntityProperty.Parameters, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.parameters); - addEntry(EntityProperty.PhenomenonTime, QObservations.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QObservations qPath) -> qPath.phenomenonTimeStart); - addEntry(EntityProperty.PhenomenonTime, QObservations.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QObservations qPath) -> qPath.phenomenonTimeEnd); - addEntry(EntityProperty.Result, QObservations.class, "n", (ExpressionFactory) (QObservations qPath) -> qPath.resultNumber); - addEntry(EntityProperty.Result, QObservations.class, "b", (ExpressionFactory) (QObservations qPath) -> qPath.resultBoolean); - addEntry(EntityProperty.Result, QObservations.class, "s", (ExpressionFactory) (QObservations qPath) -> qPath.resultString); - addEntry(EntityProperty.Result, QObservations.class, "j", (ExpressionFactory) (QObservations qPath) -> qPath.resultJson); - addEntry(EntityProperty.Result, QObservations.class, "t", (ExpressionFactory) (QObservations qPath) -> qPath.resultType); - addEntry(EntityProperty.ResultQuality, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.resultQuality); - addEntry(EntityProperty.ResultTime, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.resultTime); - addEntry(EntityProperty.ValidTime, QObservations.class, KEY_TIME_INTERVAL_START, (ExpressionFactory) (QObservations qPath) -> qPath.validTimeStart); - addEntry(EntityProperty.ValidTime, QObservations.class, KEY_TIME_INTERVAL_END, (ExpressionFactory) (QObservations qPath) -> qPath.validTimeEnd); - addEntry(NavigationProperty.FeatureOfInterest, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.featureId); - addEntry(NavigationProperty.Datastream, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.datastreamId); - addEntry(NavigationProperty.MultiDatastream, QObservations.class, (ExpressionFactory) (QObservations qPath) -> qPath.multiDatastreamId); - - addEntry(EntityProperty.Id, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.id); - addEntry(EntityProperty.Name, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.name); - addEntry(EntityProperty.Description, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.description); - addEntry(EntityProperty.EncodingType, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.encodingType); - addEntry(EntityProperty.Metadata, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.metadata); - addEntry(EntityProperty.Properties, QSensors.class, (ExpressionFactory) (QSensors qPath) -> qPath.properties); - - addEntry(EntityProperty.Id, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.id); - addEntry(EntityProperty.SelfLink, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.id); - addEntry(EntityProperty.Name, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.name); - addEntry(EntityProperty.Description, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.description); - addEntry(EntityProperty.Properties, QThings.class, (ExpressionFactory) (QThings qPath) -> qPath.properties); - } - - /** - * - * @param qPath The path to get expressions for. - * @param target The list to add to. If null a new list will be created. - * @return The target list, or a new list if target was null. - */ - public static List> expressionsForClass(Path qPath, List> target) { - List list = allForClass.get(qPath.getClass()); - if (target == null) { - target = new ArrayList<>(); - } - for (ExpressionFactory f : list) { - target.add(f.get(qPath)); - } - return target; - } - - public static Expression expressionForProperty(EntityProperty property, Path qPath) { - Map innerMap = epMapSingle.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("ObservedProperty has no property called " + property.toString()); - } - return innerMap.get(qPath.getClass()).get(qPath); - } - - /** - * Get a list of expressions for the given property and path. Add it to the - * given list, or a new list. - * - * @param property The property to get expressions for. - * @param qPath The path to get expressions for. - * @param target The list to add to. If null a new list will be created. - * @return The target list, or a new list if target was null. - */ - public static List> expressionsForProperty(EntityProperty property, Path qPath, List< Expression> target) { - Map> innerMap = epMapMulti.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("ObservedProperty has no property called " + property.toString()); - } - Map coreMap = innerMap.get(qPath.getClass()); - if (target == null) { - target = new ArrayList<>(); - } - for (Map.Entry es : coreMap.entrySet()) { - target.add(es.getValue().get(qPath)); - } - return target; - } - - /** - * Get a Map of expressions for the given property and path. Add it to the - * given Map, or a new Map. - * - * @param property The property to get expressions for. - * @param qPath The path to get expressions for. - * @param target The Map to add to. If null a new Map will be created. - * @return The target Map, or a new Map if target was null. - */ - public static Map> expressionsForProperty(EntityProperty property, Path qPath, Map> target) { - Map> innerMap = epMapMulti.get(property); - if (innerMap == null) { - throw new IllegalArgumentException("We do not know any property called " + property.toString()); - } - Map coreMap = innerMap.get(qPath.getClass()); - if (coreMap == null) { - throw new IllegalArgumentException("No property called " + property.toString() + " for " + qPath.getClass()); - } - if (target == null) { - target = new LinkedHashMap<>(); - } - for (Map.Entry es : coreMap.entrySet()) { - target.put(es.getKey(), es.getValue().get(qPath)); - } - return target; - } - - private static void addEntry(Property p, Class c, ExpressionFactory f) { - addEntrySingle(p, c, f); - addEntryMulti(p, c, null, f); - addToAll(c, f); - } - - private static void addEntry(Property p, Class c, String name, ExpressionFactory f) { - addEntrySingle(p, c, f); - addEntryMulti(p, c, name, f); - addToAll(c, f); - } - - private static void addToAll(Class c, ExpressionFactory f) { - List list = allForClass.get(c); - if (list == null) { - list = new ArrayList<>(); - allForClass.put(c, list); - } - list.add(f); - } - - private static void addEntrySingle(Property p, Class c, ExpressionFactory f) { - Map innerMap = epMapSingle.get(p); - if (innerMap == null) { - innerMap = new HashMap<>(); - epMapSingle.put(p, innerMap); - } - if (innerMap.containsKey(c)) { - LOGGER.trace("Class {} already has a registration for {}.", c.getName(), p); - return; - } - innerMap.put(c, f); - } - - private static void addEntryMulti(Property p, Class c, String name, ExpressionFactory f) { - Map> innerMap = epMapMulti.get(p); - if (innerMap == null) { - innerMap = new HashMap<>(); - epMapMulti.put(p, innerMap); - } - Map coreMap = innerMap.get(c); - if (coreMap == null) { - coreMap = new LinkedHashMap<>(); - innerMap.put(c, coreMap); - } - if (name == null) { - name = Integer.toString(coreMap.size()); - } - coreMap.put(name, f); - } -} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/UuidId.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/UuidId.java index a96ee80bc..5ad6d4f0b 100644 --- a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/UuidId.java +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/UuidId.java @@ -17,7 +17,7 @@ */ package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; import java.util.Objects; import java.util.UUID; @@ -40,7 +40,7 @@ public UuidId(String value) { @Override public BasicPersistenceType getBasicPersistenceType() { - return BasicPersistenceType.ByteArray; + return BasicPersistenceType.BYTEARRAY; } @Override @@ -58,10 +58,13 @@ public void fromBasicPersitenceType(Object data) { } @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.value); - return hash; + public UUID getValue() { + return value; + } + + @Override + public String getUrl() { + return "'" + value.toString() + "'"; } @Override @@ -76,28 +79,17 @@ public boolean equals(Object obj) { return false; } final UuidId other = (UuidId) obj; - if (!Objects.equals(this.value, other.value)) { - return false; - } - return true; + return Objects.equals(this.value, other.value); } @Override - public UUID getValue() { - return value; - } - - @Override - public String getUrl() { - return "'" + value.toString() + "'"; + public int hashCode() { + return Objects.hash(value); } @Override public String toString() { - if (value == null) { - return "null"; - } - return value.toString(); + return Objects.toString(getValue()); } } diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QDatastreamsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QDatastreamsUuid.java new file mode 100644 index 000000000..bebc457dd --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QDatastreamsUuid.java @@ -0,0 +1,77 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import java.sql.Types; +import java.util.UUID; + +/** + * QDatastreamsUuid is a Querydsl query type for QDatastreamsUuid + */ +public class QDatastreamsUuid extends AbstractQDatastreams, java.util.UUID> { + + private static final long serialVersionUID = -1859973077; + private static final String TABLE_NAME = "DATASTREAMS"; + + public static final QDatastreamsUuid DATASTREAMS = new QDatastreamsUuid(TABLE_NAME); + + public final ComparablePath id = createComparable("id", java.util.UUID.class); + + public final ComparablePath obsPropertyId = createComparable("obsPropertyId", java.util.UUID.class); + + public final ComparablePath sensorId = createComparable("sensorId", java.util.UUID.class); + + public final ComparablePath thingId = createComparable("thingId", java.util.UUID.class); + + public QDatastreamsUuid(String variable) { + super(QDatastreamsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(obsPropertyId, ColumnMetadata.named("OBS_PROPERTY_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(sensorId, ColumnMetadata.named("SENSOR_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + /** + * @return the obsPropertyId + */ + @Override + public ComparablePath getObsPropertyId() { + return obsPropertyId; + } + + /** + * @return the sensorId + */ + @Override + public ComparablePath getSensorId() { + return sensorId; + } + + /** + * @return the thingId + */ + @Override + public ComparablePath getThingId() { + return thingId; + } + + @Override + public QDatastreamsUuid newWithAlias(String variable) { + return new QDatastreamsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QFeaturesUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QFeaturesUuid.java new file mode 100644 index 000000000..240aec32e --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QFeaturesUuid.java @@ -0,0 +1,44 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQFeatures; +import java.sql.Types; +import java.util.UUID; + +/** + * QFeaturesUuid is a Querydsl query type for QFeaturesUuid + */ +public class QFeaturesUuid extends AbstractQFeatures, UUID> { + + private static final long serialVersionUID = 175404379; + private static final String TABLE_NAME = "FEATURES"; + + public static final QFeaturesUuid FEATURES = new QFeaturesUuid(TABLE_NAME); + + public final ComparablePath id = createComparable("id", UUID.class); + + public QFeaturesUuid(String variable) { + super(QFeaturesUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + @Override + public QFeaturesUuid newWithAlias(String variable) { + return new QFeaturesUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QHistLocationsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QHistLocationsUuid.java new file mode 100644 index 000000000..8c30d7b0b --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QHistLocationsUuid.java @@ -0,0 +1,52 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import java.sql.Types; +import java.util.UUID; + +/** + * QHistLocationsUuid is a Querydsl query type for QHistLocationsUuid + */ +public class QHistLocationsUuid extends AbstractQHistLocations, UUID> { + + private static final long serialVersionUID = -1683099650; + private static final String TABLE_NAME = "HIST_LOCATIONS"; + + public static final QHistLocationsUuid HISTLOCATIONS = new QHistLocationsUuid(TABLE_NAME); + + public final ComparablePath id = createComparable("id", UUID.class); + + public final ComparablePath thingId = createComparable("thingId", UUID.class); + + public QHistLocationsUuid(String variable) { + super(QHistLocationsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + @Override + public ComparablePath getThingId() { + return thingId; + } + + @Override + public QHistLocationsUuid newWithAlias(String variable) { + return new QHistLocationsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QLocationsHistLocationsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QLocationsHistLocationsUuid.java new file mode 100644 index 000000000..215c52997 --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QLocationsHistLocationsUuid.java @@ -0,0 +1,50 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocationsHistLocations; +import java.sql.Types; +import java.util.UUID; + +/** + * QLocationsHistLocationsUuid is a Querydsl query type for + * QLocationsHistLocationsUuid + */ +public class QLocationsHistLocationsUuid extends AbstractQLocationsHistLocations, UUID> { + + private static final long serialVersionUID = 68027452; + private static final String TABLE_NAME = "LOCATIONS_HIST_LOCATIONS"; + + public static final QLocationsHistLocationsUuid LOCATIONSHISTLOCATIONS = new QLocationsHistLocationsUuid(TABLE_NAME); + + public final ComparablePath histLocationId = createComparable("histLocationId", UUID.class); + + public final ComparablePath locationId = createComparable("locationId", UUID.class); + + public QLocationsHistLocationsUuid(String variable) { + super(QLocationsHistLocationsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(histLocationId, ColumnMetadata.named("HIST_LOCATION_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(locationId, ColumnMetadata.named("LOCATION_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + @Override + public ComparablePath getLocationId() { + return locationId; + } + + @Override + public ComparablePath getHistLocationId() { + return histLocationId; + } + + @Override + public QLocationsHistLocationsUuid newWithAlias(String variable) { + return new QLocationsHistLocationsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QLocationsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QLocationsUuid.java new file mode 100644 index 000000000..d511b43af --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QLocationsUuid.java @@ -0,0 +1,52 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocations; +import java.sql.Types; +import java.util.UUID; + +/** + * QLocationsUuid is a Querydsl query type for QLocationsUuid + */ +public class QLocationsUuid extends AbstractQLocations, UUID> { + + private static final long serialVersionUID = 365881856; + private static final String TABLE_NAME = "LOCATIONS"; + + public static final QLocationsUuid LOCATIONS = new QLocationsUuid(TABLE_NAME); + + public final ComparablePath genFoiId = createComparable("genFoiId", UUID.class); + + public final ComparablePath id = createComparable("id", UUID.class); + + public QLocationsUuid(String variable) { + super(QLocationsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(genFoiId, ColumnMetadata.named("GEN_FOI_ID").ofType(Types.BINARY).withSize(2147483647)); + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + @Override + public ComparablePath getGenFoiId() { + return genFoiId; + } + + @Override + public QLocationsUuid newWithAlias(String variable) { + return new QLocationsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QMultiDatastreamsObsPropertiesUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QMultiDatastreamsObsPropertiesUuid.java new file mode 100644 index 000000000..db7f1319d --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QMultiDatastreamsObsPropertiesUuid.java @@ -0,0 +1,50 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreamsObsProperties; +import java.sql.Types; +import java.util.UUID; + +/** + * QMultiDatastreamsObsPropertiesUuid is a Querydsl query type for + * QMultiDatastreamsObsPropertiesUuid + */ +public class QMultiDatastreamsObsPropertiesUuid extends AbstractQMultiDatastreamsObsProperties, UUID> { + + private static final long serialVersionUID = 2126924485; + private static final String TABLE_NAME = "MULTI_DATASTREAMS_OBS_PROPERTIES"; + + public static final QMultiDatastreamsObsPropertiesUuid MULTIDATASTREAMSOBSPROPERTIES = new QMultiDatastreamsObsPropertiesUuid(TABLE_NAME); + + public final ComparablePath multiDatastreamId = createComparable("multiDatastreamId", UUID.class); + + public final ComparablePath obsPropertyId = createComparable("obsPropertyId", UUID.class); + + public QMultiDatastreamsObsPropertiesUuid(String variable) { + super(QMultiDatastreamsObsPropertiesUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(multiDatastreamId, ColumnMetadata.named("MULTI_DATASTREAM_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(obsPropertyId, ColumnMetadata.named("OBS_PROPERTY_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + @Override + public ComparablePath getMultiDatastreamId() { + return multiDatastreamId; + } + + @Override + public ComparablePath getObsPropertyId() { + return obsPropertyId; + } + + @Override + public QMultiDatastreamsObsPropertiesUuid newWithAlias(String variable) { + return new QMultiDatastreamsObsPropertiesUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QMultiDatastreamsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QMultiDatastreamsUuid.java new file mode 100644 index 000000000..f43bf837a --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QMultiDatastreamsUuid.java @@ -0,0 +1,57 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import java.sql.Types; +import java.util.UUID; + +/** + * QMultiDatastreamsUuid is a Querydsl query type for QMultiDatastreamsUuid + */ +public class QMultiDatastreamsUuid extends AbstractQMultiDatastreams, UUID> { + + private static final long serialVersionUID = 1620555310; + private static final String TABLE_NAME = "MULTI_DATASTREAMS"; + + public static final QMultiDatastreamsUuid MULTIDATASTREAMS = new QMultiDatastreamsUuid(TABLE_NAME); + + public final ComparablePath id = createComparable("id", UUID.class); + + public final ComparablePath sensorId = createComparable("sensorId", UUID.class); + + public final ComparablePath thingId = createComparable("thingId", UUID.class); + + public QMultiDatastreamsUuid(String variable) { + super(QMultiDatastreamsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + public void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(sensorId, ColumnMetadata.named("SENSOR_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + @Override + public ComparablePath getId() { + return id; + } + + @Override + public ComparablePath getThingId() { + return thingId; + } + + @Override + public ComparablePath getSensorId() { + return sensorId; + } + + @Override + public QMultiDatastreamsUuid newWithAlias(String variable) { + return new QMultiDatastreamsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QObsPropertiesUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QObsPropertiesUuid.java new file mode 100644 index 000000000..6cb06a66e --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QObsPropertiesUuid.java @@ -0,0 +1,44 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObsProperties; +import java.sql.Types; +import java.util.UUID; + +/** + * QObsPropertiesUuid is a Querydsl query type for QObsPropertiesUuid + */ +public class QObsPropertiesUuid extends AbstractQObsProperties, UUID> { + + private static final long serialVersionUID = 1235830773; + private static final String TABLE_NAME = "OBS_PROPERTIES"; + + public static final QObsPropertiesUuid OBSPROPERTIES = new QObsPropertiesUuid(TABLE_NAME); + + public final ComparablePath id = createComparable("id", UUID.class); + + public QObsPropertiesUuid(String variable) { + super(QObsPropertiesUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + @Override + public QObsPropertiesUuid newWithAlias(String variable) { + return new QObsPropertiesUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QObservationsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QObservationsUuid.java new file mode 100644 index 000000000..55ed4f07f --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QObservationsUuid.java @@ -0,0 +1,68 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import java.sql.Types; +import java.util.UUID; + +/** + * QObservationsUuid is a Querydsl query type for QObservationsUuid + */ +public class QObservationsUuid extends AbstractQObservations, UUID> { + + private static final long serialVersionUID = -1085407259; + private static final String TABLE_NAME = "OBSERVATIONS"; + + public static final QObservationsUuid OBSERVATIONS = new QObservationsUuid(TABLE_NAME); + + public final ComparablePath datastreamId = createComparable("datastreamId", UUID.class); + + public final ComparablePath featureId = createComparable("featureId", UUID.class); + + public final ComparablePath id = createComparable("id", UUID.class); + + public final ComparablePath multiDatastreamId = createComparable("multiDatastreamId", UUID.class); + + public QObservationsUuid(String variable) { + super(QObservationsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(datastreamId, ColumnMetadata.named("DATASTREAM_ID").ofType(Types.BINARY).withSize(2147483647)); + addMetadata(featureId, ColumnMetadata.named("FEATURE_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(multiDatastreamId, ColumnMetadata.named("MULTI_DATASTREAM_ID").ofType(Types.BINARY).withSize(2147483647)); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + @Override + public ComparablePath getDatastreamId() { + return datastreamId; + } + + @Override + public ComparablePath getFeatureId() { + return featureId; + } + + @Override + public ComparablePath getMultiDatastreamId() { + return multiDatastreamId; + } + + @Override + public QObservationsUuid newWithAlias(String variable) { + return new QObservationsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QSensorsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QSensorsUuid.java new file mode 100644 index 000000000..fd0c57851 --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QSensorsUuid.java @@ -0,0 +1,44 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQSensors; +import java.sql.Types; +import java.util.UUID; + +/** + * QSensorsUuid is a Querydsl query type for QSensorsUuid + */ +public class QSensorsUuid extends AbstractQSensors, UUID> { + + private static final long serialVersionUID = 748484379; + private static final String TABLE_NAME = "SENSORS"; + + public static final QSensorsUuid SENSORS = new QSensorsUuid(TABLE_NAME); + + public final ComparablePath id = createComparable("id", UUID.class); + + public QSensorsUuid(String variable) { + super(QSensorsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + @Override + public QSensorsUuid newWithAlias(String variable) { + return new QSensorsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QThingsLocationsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QThingsLocationsUuid.java new file mode 100644 index 000000000..8cb00c54f --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QThingsLocationsUuid.java @@ -0,0 +1,49 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import java.sql.Types; +import java.util.UUID; + +/** + * QThingsLocationsUuid is a Querydsl query type for QThingsLocationsUuid + */ +public class QThingsLocationsUuid extends AbstractQThingsLocations, UUID> { + + private static final long serialVersionUID = -1915253573; + private static final String TABLE_NAME = "THINGS_LOCATIONS"; + + public static final QThingsLocationsUuid THINGSLOCATIONS = new QThingsLocationsUuid(TABLE_NAME); + + public final ComparablePath locationId = createComparable("locationId", UUID.class); + + public final ComparablePath thingId = createComparable("thingId", UUID.class); + + public QThingsLocationsUuid(String variable) { + super(QThingsLocationsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(locationId, ColumnMetadata.named("LOCATION_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + addMetadata(thingId, ColumnMetadata.named("THING_ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + @Override + public ComparablePath getLocationId() { + return locationId; + } + + @Override + public ComparablePath getThingId() { + return thingId; + } + + @Override + public QThingsLocationsUuid newWithAlias(String variable) { + return new QThingsLocationsUuid(variable); + } + +} diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QThingsUuid.java b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QThingsUuid.java new file mode 100644 index 000000000..e050374d2 --- /dev/null +++ b/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/relationalpaths/QThingsUuid.java @@ -0,0 +1,46 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.relationalpaths; + +import static com.querydsl.core.types.PathMetadataFactory.forVariable; +import com.querydsl.core.types.dsl.ComparablePath; +import com.querydsl.sql.ColumnMetadata; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThings; +import java.sql.Types; +import java.util.UUID; + +/** + * QThingsUuid is a Querydsl query type for QThingsUuid + */ +public class QThingsUuid extends AbstractQThings, UUID> { + + private static final long serialVersionUID = -1745724957; + private static final String TABLE_NAME = "THINGS"; + + public static final QThingsUuid THINGS = new QThingsUuid(TABLE_NAME); + + public final ComparablePath id = createComparable("id", UUID.class); + + public final com.querydsl.sql.PrimaryKey thingsPkey = createPrimaryKey(id); + + public QThingsUuid(String variable) { + super(QThingsUuid.class, forVariable(variable), "PUBLIC", TABLE_NAME); + addMetadata(); + } + + private void addMetadata() { + addMetadata(id, ColumnMetadata.named("ID").ofType(Types.BINARY).withSize(2147483647).notNull()); + } + + /** + * @return the id + */ + @Override + public ComparablePath getId() { + return id; + } + + @Override + public QThingsUuid newWithAlias(String variable) { + return new QThingsUuid(variable); + } + +} diff --git a/FROST-Server.SQL/pom.xml b/FROST-Server.SQL/pom.xml index 0aaaee472..1d2d8c5ac 100644 --- a/FROST-Server.SQL/pom.xml +++ b/FROST-Server.SQL/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT ../pom.xml FROST-Server.SQL @@ -30,6 +30,11 @@ ${junit.version} test
+ + org.apache.commons + commons-dbcp2 + ${dbcp2.version} + org.postgresql postgresql @@ -42,11 +47,6 @@ ${postgis.version} provided - - com.h2database - h2 - ${h2.version} - org.slf4j slf4j-api @@ -149,7 +149,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/DataSize.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/DataSize.java index d3091e010..58c6fd418 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/DataSize.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/DataSize.java @@ -18,25 +18,24 @@ package de.fraunhofer.iosb.ilt.sta.persistence.postgres; /** - * A wrapper for the dataSize so it can be passed to, and changed by, the - * Factories. + * A wrapper for the size so it can be passed to, and changed by, the Factories. */ public class DataSize { - private long dataSize; + private long size; /** * @return the DataSize */ public long getDataSize() { - return dataSize; + return size; } /** * @param dataSize the DataSize to set */ public void setDataSize(long dataSize) { - this.dataSize = dataSize; + this.size = dataSize; } /** @@ -45,7 +44,7 @@ public void setDataSize(long dataSize) { * @param amount the amount to increase the size with. */ public void increase(long amount) { - dataSize += amount; + size += amount; } } diff --git a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/EntityCreator.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/EntityCreator.java similarity index 65% rename from FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/EntityCreator.java rename to FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/EntityCreator.java index be67930ce..0e5509f49 100644 --- a/FROST-Server.SQL.PGUuid/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/uuidid/EntityCreator.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/EntityCreator.java @@ -15,7 +15,7 @@ * 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.sta.persistence.postgres.uuidid; +package de.fraunhofer.iosb.ilt.sta.persistence.postgres; import com.fasterxml.jackson.databind.node.ArrayNode; import com.mysema.commons.lang.CloseableIterator; @@ -33,8 +33,7 @@ import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; import de.fraunhofer.iosb.ilt.sta.path.ResourcePathVisitor; -import de.fraunhofer.iosb.ilt.sta.persistence.PersistenceManager; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.EntityFactory; import de.fraunhofer.iosb.ilt.sta.query.Expand; import de.fraunhofer.iosb.ilt.sta.query.Query; import de.fraunhofer.iosb.ilt.sta.util.UrlHelper; @@ -48,13 +47,13 @@ * * @author scf */ -class EntityCreator implements ResourcePathVisitor { +public class EntityCreator implements ResourcePathVisitor { /** * The logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(EntityCreator.class); - private final PersistenceManager pm; + private final PostgresPersistenceManager pm; private final ResourcePath path; private final Query query; private final SQLQuery sqlQuery; @@ -72,7 +71,7 @@ class EntityCreator implements ResourcePathVisitor { * @param query The query parameters to use when fetching expanded items. * @param sqlQuery The sql query to use for fetching items. */ - public EntityCreator(PersistenceManager pm, ResourcePath path, Query query, SQLQuery sqlQuery) { + public EntityCreator(PostgresPersistenceManager pm, ResourcePath path, Query query, SQLQuery sqlQuery) { this.pm = pm; this.path = path; this.query = query; @@ -87,7 +86,7 @@ public Object getEntity() { * If resultObject is a property or sub-property, and we are not using * $value, then the resultObject is encapsulated in a Map, using this key. * - * @return The name of the resultObject in the map. + * @return The entitiyName of the resultObject in the map. */ public String getEntityName() { return entityName; @@ -104,7 +103,8 @@ public void visit(EntityPathElement element) { return; } - PropertyHelper.entityFromTupleFactory factory = PropertyHelper.getFactoryFor(element.getEntityType().getImplementingClass()); + EntityFactory factory; + factory = pm.getEntityFactories().getFactoryFor(element.getEntityType()); Entity entity = factory.create(results.get(0), query, new DataSize()); if (entity == null) { @@ -114,69 +114,68 @@ public void visit(EntityPathElement element) { resultObject = entity; } - private void expandEntity(Entity e, Query query) { + private void expandEntity(Entity entity, Query query) { if (query == null) { return; } for (Expand expand : query.getExpand()) { - ResourcePath ePath = new ResourcePath(path.getServiceRootUrl(), null); - ResourcePathElement parentCollection = new EntitySetPathElement(e.getEntityType(), null); - ePath.addPathElement(parentCollection, false, false); - ResourcePathElement parent = new EntityPathElement(e.getId(), e.getEntityType(), parentCollection); - ePath.addPathElement(parent, false, true); - - NavigationProperty firstNp = expand.getPath().get(0); - NavigableElement existing = null; - { - Object o = e.getProperty(firstNp); - if (o instanceof NavigableElement) { - existing = (NavigableElement) o; - } - } + addExpandToEntity(entity, expand, query); + } + } - if (firstNp.isSet) { - EntitySetPathElement child = new EntitySetPathElement(firstNp.type, parent); - ePath.addPathElement(child, true, false); - } else { - EntityPathElement child = new EntityPathElement(null, firstNp.type, parent); - ePath.addPathElement(child, true, false); + private void addExpandToEntity(Entity entity, Expand expand, Query query1) { + ResourcePath ePath = new ResourcePath(path.getServiceRootUrl(), null); + ResourcePathElement parentCollection = new EntitySetPathElement(entity.getEntityType(), null); + ePath.addPathElement(parentCollection, false, false); + ResourcePathElement parent = new EntityPathElement(entity.getId(), entity.getEntityType(), parentCollection); + ePath.addPathElement(parent, false, true); + NavigationProperty firstNp = expand.getPath().get(0); + NavigableElement existing = null; + Object o = entity.getProperty(firstNp); + if (o instanceof NavigableElement) { + existing = (NavigableElement) o; + } + if (firstNp.isSet) { + EntitySetPathElement child = new EntitySetPathElement(firstNp.type, parent); + ePath.addPathElement(child, true, false); + } else { + EntityPathElement child = new EntityPathElement(null, firstNp.type, parent); + ePath.addPathElement(child, true, false); + } + Object child; + Query subQuery; + if (expand.getPath().size() == 1) { + // This was the last element in the expand path. The query is for this element. + subQuery = expand.getSubQuery(); + if (subQuery == null) { + subQuery = new Query(query1.getSettings()); } - - Object child; - Query subQuery; - if (expand.getPath().size() == 1) { - // This was the last element in the expand path. The query is for this element. - subQuery = expand.getSubQuery(); - if (subQuery == null) { - subQuery = new Query(query.getSettings()); - } - } else { - // This is not the last element in the expand path. The query is not for this element. - subQuery = new Query(query.getSettings()); - Expand subExpand = new Expand(); - subExpand.getPath().addAll(expand.getPath()); - subExpand.getPath().remove(0); - subExpand.setSubQuery(expand.getSubQuery()); - subQuery.addExpand(subExpand); - if (query.getCount().isPresent()) { - subQuery.setCount(query.isCountOrDefault()); - } + } else { + // This is not the last element in the expand path. The query is not for this element. + subQuery = new Query(query1.getSettings()); + Expand subExpand = new Expand(); + subExpand.getPath().addAll(expand.getPath()); + subExpand.getPath().remove(0); + subExpand.setSubQuery(expand.getSubQuery()); + subQuery.addExpand(subExpand); + if (query1.getCount().isPresent()) { + subQuery.setCount(query1.isCountOrDefault()); } + } + if (existing == null || !existing.isExportObject()) { + child = pm.get(ePath, subQuery); + entity.setProperty(firstNp, child); + } else if (existing instanceof EntitySet) { + expandEntitySet((EntitySet) existing, subQuery); + } else if (existing instanceof Entity) { + expandEntity((Entity) existing, subQuery); + } + } - if (existing == null || !existing.isExportObject()) { - child = pm.get(ePath, subQuery); - e.setProperty(firstNp, child); - } else if (existing instanceof EntitySet) { - EntitySet entitySet = (EntitySet) existing; - for (Object subEntity : entitySet) { - if (subEntity instanceof Entity) { - Entity entity = (Entity) subEntity; - expandEntity(entity, subQuery); - } - } - } else if (existing instanceof Entity) { - Entity entity = (Entity) existing; - expandEntity(entity, subQuery); + private void expandEntitySet(EntitySet entitySet, Query subQuery) { + for (Object subEntity : entitySet) { + if (subEntity instanceof Entity) { + expandEntity((Entity) subEntity, subQuery); } } } @@ -185,25 +184,24 @@ private void expandEntity(Entity e, Query query) { public void visit(EntitySetPathElement element) { int top = query.getTopOrDefault(); - sqlQuery.limit(top + 1); + sqlQuery.limit(1l + top); + + int skip = query.getSkip(0); + sqlQuery.offset(skip); - int skip = 0; - if (query.getSkip().isPresent()) { - skip = query.getSkip().get(); - sqlQuery.offset(skip); - } long start = System.currentTimeMillis(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Query: {}", sqlQuery.getSQL().getSQL()); } - CloseableIterator results = sqlQuery.iterate(); //fetch(); + CloseableIterator results = sqlQuery.iterate(); if (LOGGER.isDebugEnabled()) { long end = System.currentTimeMillis(); LOGGER.debug("Query executed in {} ms.", end - start); } - PropertyHelper.entityFromTupleFactory factory = PropertyHelper.getFactoryFor(element.getEntityType().getImplementingClass()); - EntitySet entitySet = PropertyHelper.createSetFromTuples(factory, results, query, pm.getCoreSettings().getDataSizeMax()); + EntityFactory factory; + factory = pm.getEntityFactories().getFactoryFor(element.getEntityType()); + EntitySet entitySet = pm.getEntityFactories().createSetFromTuples(factory, results, query, pm.getCoreSettings().getDataSizeMax()); if (entitySet == null) { throw new IllegalStateException("Empty set!"); @@ -237,7 +235,7 @@ public void visit(PropertyPathElement element) { if (Entity.class.isAssignableFrom(resultObject.getClass())) { Object propertyValue = ((Entity) resultObject).getProperty(element.getProperty()); Map entityMap = new HashMap<>(); - entityName = element.getProperty().name; + entityName = element.getProperty().entitiyName; entityMap.put(entityName, propertyValue); resultObject = entityMap; } diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/EntityFactories.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/EntityFactories.java new file mode 100644 index 000000000..43950d620 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/EntityFactories.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2016 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.sta.persistence.postgres; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mysema.commons.lang.CloseableIterator; +import com.querydsl.core.Tuple; +import com.querydsl.core.dml.StoreClause; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.spatial.GeometryPath; +import com.querydsl.sql.SQLQuery; +import com.querydsl.sql.SQLQueryFactory; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.custom.GeoJsonDeserializier; +import de.fraunhofer.iosb.ilt.sta.json.serialize.GeoJsonSerializer; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; +import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; +import de.fraunhofer.iosb.ilt.sta.model.Sensor; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.builder.FeatureOfInterestBuilder; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySetImpl; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; +import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; +import de.fraunhofer.iosb.ilt.sta.persistence.IdManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.DatastreamFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.EntityFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.FeatureOfInterestFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.HistoricalLocationFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.LocationFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.MultiDatastreamFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.ObservationFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.ObservedPropertyFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.SensorFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.ThingFactory; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQFeatures; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObsProperties; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQSensors; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThings; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.io.IOException; +import java.sql.Timestamp; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import org.geojson.Crs; +import org.geojson.Feature; +import org.geojson.GeoJsonObject; +import org.geojson.jackson.CrsType; +import org.geolatte.common.dataformats.json.jackson.JsonException; +import org.geolatte.common.dataformats.json.jackson.JsonMapper; +import org.geolatte.geom.Geometry; +import org.joda.time.Interval; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author scf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class EntityFactories & Path, J> { + + public static final TypeReference> TYPE_LIST_STRING = new TypeReference>() { + // Empty on purpose. + }; + public static final TypeReference> TYPE_LIST_UOM = new TypeReference>() { + // Empty on purpose. + }; + public static final String CAN_NOT_BE_NULL = " can not be null."; + public static final String CHANGED_MULTIPLE_ROWS = "Update changed multiple rows."; + public static final String NO_ID_OR_NOT_FOUND = " with no id or non existing."; + public static final String CREATED_HL = "Created historicalLocation {}"; + public static final String LINKED_L_TO_HL = "Linked location {} to historicalLocation {}."; + public static final String UNLINKED_L_FROM_T = "Unlinked {} locations from Thing {}."; + public static final String LINKED_L_TO_T = "Linked Location {} to Thing {}."; + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(EntityFactories.class); + private static ObjectMapper formatter; + + public final IdManager idManager; + public final QCollection qCollection; + + public final DatastreamFactory datastreamFactory; + public final MultiDatastreamFactory multiDatastreamFactory; + public final ThingFactory thingFactory; + public final FeatureOfInterestFactory featureOfInterestFactory; + public final HistoricalLocationFactory historicalLocationFactory; + public final LocationFactory locationFactory; + public final SensorFactory sensorFactory; + public final ObservationFactory observationFactory; + public final ObservedPropertyFactory observedPropertyFactory; + + private final Map> factoryPerEntity = new EnumMap<>(EntityType.class); + + public EntityFactories(IdManager idManager, QCollection qCollection) { + this.idManager = idManager; + this.qCollection = qCollection; + + String defaultPrefix = PathSqlBuilderImp.ALIAS_PREFIX + "1"; + + datastreamFactory = new DatastreamFactory<>(this, qCollection.qDatastreams.newWithAlias(defaultPrefix)); + multiDatastreamFactory = new MultiDatastreamFactory<>(this, qCollection.qMultiDatastreams.newWithAlias(defaultPrefix)); + thingFactory = new ThingFactory<>(this, qCollection.qThings.newWithAlias(defaultPrefix)); + featureOfInterestFactory = new FeatureOfInterestFactory<>(this, qCollection.qFeatures.newWithAlias(defaultPrefix)); + historicalLocationFactory = new HistoricalLocationFactory<>(this, qCollection.qHistLocations.newWithAlias(defaultPrefix)); + locationFactory = new LocationFactory<>(this, qCollection.qLocations.newWithAlias(defaultPrefix)); + sensorFactory = new SensorFactory<>(this, qCollection.qSensors.newWithAlias(defaultPrefix)); + observationFactory = new ObservationFactory<>(this, qCollection.qObservations.newWithAlias(defaultPrefix)); + observedPropertyFactory = new ObservedPropertyFactory<>(this, qCollection.qObsProperties.newWithAlias(defaultPrefix)); + + factoryPerEntity.put(EntityType.DATASTREAM, datastreamFactory); + factoryPerEntity.put(EntityType.MULTIDATASTREAM, multiDatastreamFactory); + factoryPerEntity.put(EntityType.THING, thingFactory); + factoryPerEntity.put(EntityType.FEATUREOFINTEREST, featureOfInterestFactory); + factoryPerEntity.put(EntityType.HISTORICALLOCATION, historicalLocationFactory); + factoryPerEntity.put(EntityType.LOCATION, locationFactory); + factoryPerEntity.put(EntityType.SENSOR, sensorFactory); + factoryPerEntity.put(EntityType.OBSERVATION, observationFactory); + factoryPerEntity.put(EntityType.OBSERVEDPROPERTY, observedPropertyFactory); + } + + public QCollection getQCollection() { + return qCollection; + } + + public EntitySet createSetFromTuples(EntityFactory factory, CloseableIterator tuples, Query query, long maxDataSize) { + EntitySet entitySet = new EntitySetImpl<>(factory.getEntityType()); + int count = 0; + DataSize size = new DataSize(); + int top = query.getTopOrDefault(); + while (tuples.hasNext()) { + Tuple tuple = tuples.next(); + entitySet.add(factory.create(tuple, query, size)); + count++; + if (count >= top) { + return entitySet; + } + if (size.getDataSize() > maxDataSize) { + LOGGER.debug("Size limit reached: {} > {}.", size.getDataSize(), maxDataSize); + return entitySet; + } + } + return entitySet; + } + + /** + * Get the factory for the given entity class, using the default alias + * PathSqlBuilderLong.ALIAS_PREFIX + "1". + * + * @param The type of entity to get the factory for. + * @param type The type of the entity to get the factory for. + * @return the factory for the given entity class. + */ + public EntityFactory getFactoryFor(EntityType type) { + EntityFactory factory = factoryPerEntity.get(type); + if (factory == null) { + throw new AssertionError("No factory found for " + type); + } + return (EntityFactory) factory; + } + + public J getIdFromTuple(Tuple t, I path) { + return t.get(path); + } + + public Id idFromObject(J id) { + return idManager.fromObject(id); + } + + public Datastream datastreamFromId(Tuple tuple, I path) { + return datastreamFromId(getIdFromTuple(tuple, path)); + } + + public Datastream datastreamFromId(J id) { + if (id == null) { + return null; + } + Datastream ds = new Datastream(true, idManager.fromObject(id)); + ds.setExportObject(false); + return ds; + } + + public MultiDatastream multiDatastreamFromId(Tuple tuple, I path) { + return multiDatastreamFromId(getIdFromTuple(tuple, path)); + } + + public MultiDatastream multiDatastreamFromId(J id) { + if (id == null) { + return null; + } + MultiDatastream ds = new MultiDatastream(); + ds.setId(idManager.fromObject(id)); + ds.setExportObject(false); + return ds; + } + + public FeatureOfInterest featureOfInterestFromId(Tuple tuple, I path) { + return featureOfInterestFromId(getIdFromTuple(tuple, path)); + } + + public FeatureOfInterest featureOfInterestFromId(J id) { + if (id == null) { + return null; + } + FeatureOfInterest foi = new FeatureOfInterest(); + foi.setId(idManager.fromObject(id)); + foi.setExportObject(false); + return foi; + } + + public ObservedProperty observedProperyFromId(Tuple tuple, I path) { + return observedProperyFromId(getIdFromTuple(tuple, path)); + } + + public ObservedProperty observedProperyFromId(J id) { + if (id == null) { + return null; + } + ObservedProperty op = new ObservedProperty(); + op.setId(idManager.fromObject(id)); + op.setExportObject(false); + return op; + } + + public Sensor sensorFromId(Tuple tuple, I path) { + return sensorFromId(getIdFromTuple(tuple, path)); + } + + public Sensor sensorFromId(J id) { + if (id == null) { + return null; + } + Sensor sensor = new Sensor(); + sensor.setId(idManager.fromObject(id)); + sensor.setExportObject(false); + return sensor; + } + + public Thing thingFromId(Tuple tuple, I path) { + return thingFromId(getIdFromTuple(tuple, path)); + } + + public Thing thingFromId(J id) { + if (id == null) { + return null; + } + Thing thing = new Thing(); + thing.setId(idManager.fromObject(id)); + thing.setExportObject(false); + return thing; + } + + public FeatureOfInterest generateFeatureOfInterest(PostgresPersistenceManager pm, Id datastreamId, boolean isMultiDatastream) throws NoSuchEntityException, IncompleteEntityException { + J dsId = (J) datastreamId.getValue(); + SQLQueryFactory qf = pm.createQueryFactory(); + AbstractQLocations ql = qCollection.qLocations; + AbstractQThingsLocations qtl = qCollection.qThingsLocations; + AbstractQThings qt = qCollection.qThings; + AbstractQDatastreams qd = qCollection.qDatastreams; + AbstractQMultiDatastreams qmd = qCollection.qMultiDatastreams; + + SQLQuery query = qf.select(ql.getId(), ql.getGenFoiId(), ql.encodingType) + .from(ql) + .innerJoin(qtl).on(ql.getId().eq(qtl.getLocationId())) + .innerJoin(qt).on(qt.getId().eq(qtl.getThingId())); + if (isMultiDatastream) { + query.innerJoin(qmd).on(qmd.getThingId().eq(qt.getId())) + .where(qmd.getId().eq(dsId)); + } else { + query.innerJoin(qd).on(qd.getThingId().eq(qt.getId())) + .where(qd.getId().eq(dsId)); + } + List tuples = query.fetch(); + if (tuples.isEmpty()) { + // Can not generate foi from Thing with no locations. + throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); + } + // See if any of the locations have a generated foi. + // Also track if any of the location has a supported encoding type. + J genFoiId = null; + J locationId = null; + for (Tuple tuple : tuples) { + genFoiId = tuple.get(ql.getGenFoiId()); + if (genFoiId != null) { + break; + } + String encodingType = tuple.get(ql.encodingType); + if (encodingType != null && GeoJsonDeserializier.ENCODINGS.contains(encodingType.toLowerCase())) { + locationId = tuple.get(ql.getId()); + } + } + // Either genFoiId will have a value, if a generated foi was found, + // Or locationId will have a value if a supported encoding type was found. + + FeatureOfInterest foi; + if (genFoiId != null) { + foi = new FeatureOfInterest(); + foi.setId(idFromObject(genFoiId)); + } else if (locationId != null) { + query = qf.select(ql.getId(), ql.encodingType, ql.location) + .from(ql) + .where(ql.getId().eq(locationId)); + Tuple tuple = query.fetchOne(); + if (tuple == null) { + // Can not generate foi from Thing with no locations. + // Should not happen, since the query succeeded just before. + throw new NoSuchEntityException("Can not generate foi for Thing with no locations."); + } + String encoding = tuple.get(ql.encodingType); + String locString = tuple.get(ql.location); + Object locObject = Utils.locationFromEncoding(encoding, locString); + foi = new FeatureOfInterestBuilder() + .setName("FoI for location " + locationId) + .setDescription("Generated from location " + locationId) + .setEncodingType(encoding) + .setFeature(locObject) + .build(); + featureOfInterestFactory.insert(pm, foi); + J foiId = (J) foi.getId().getValue(); + qf.update(ql) + .set(ql.getGenFoiId(), (J) foi.getId().getValue()) + .where(ql.getId().eq(locationId)) + .execute(); + LOGGER.debug("Generated foi {} from Location {}.", foiId, locationId); + } else { + // Can not generate foi from Thing with no locations. + throw new NoSuchEntityException("Can not generate foi for Thing, all locations have an un supported encoding type."); + } + return foi; + } + + public void insertUserDefinedId(PostgresPersistenceManager pm, T clause, Path idPath, Entity entity) throws IncompleteEntityException { + IdGenerationHandler idhandler = pm.createIdGenerationHanlder(entity); + if (idhandler.useClientSuppliedId()) { + idhandler.modifyClientSuppliedId(); + clause.set(idPath, (J) idhandler.getIdValue()); + } + } + + /** + * Throws an exception if the entity has an id, but does not exist or if the + * entity can not be created. + * + * @param pm the persistenceManager + * @param e The Entity to check. + * @throws NoSuchEntityException If the entity has an id, but does not + * exist. + * @throws IncompleteEntityException If the entity has no id, but is not + * complete and can thus not be created. + */ + public void entityExistsOrCreate(PostgresPersistenceManager pm, Entity e) throws NoSuchEntityException, IncompleteEntityException { + if (e == null) { + throw new NoSuchEntityException("No entity!"); + } + + if (e.getId() == null) { + e.complete(); + // no id but complete -> create + pm.insert(e); + return; + } + + if (entityExists(pm, e)) { + return; + } + + // check if this is an incomplete entity + try { + e.complete(); + } catch (IncompleteEntityException exc) { + // not complete and link entity does not exist + throw new NoSuchEntityException("No such entity '" + e.getEntityType() + "' with id " + e.getId().getValue()); + } + + // complete with id -> create + pm.insert(e); + } + + public boolean entityExists(PostgresPersistenceManager pm, Entity e) { + if (e == null || e.getId() == null) { + return false; + } + J id = (J) e.getId().getValue(); + SQLQueryFactory qFactory = pm.createQueryFactory(); + long count = 0; + switch (e.getEntityType()) { + case DATASTREAM: + AbstractQDatastreams d = qCollection.qDatastreams; + count = qFactory.select() + .from(d) + .where(d.getId().eq(id)) + .fetchCount(); + break; + + case MULTIDATASTREAM: + AbstractQMultiDatastreams md = qCollection.qMultiDatastreams; + count = qFactory.select() + .from(md) + .where(md.getId().eq(id)) + .fetchCount(); + break; + + case FEATUREOFINTEREST: + AbstractQFeatures foi = qCollection.qFeatures; + count = qFactory.select() + .from(foi) + .where(foi.getId().eq(id)) + .fetchCount(); + break; + + case HISTORICALLOCATION: + AbstractQHistLocations h = qCollection.qHistLocations; + count = qFactory.select() + .from(h) + .where(h.getId().eq(id)) + .fetchCount(); + break; + + case LOCATION: + AbstractQLocations l = qCollection.qLocations; + count = qFactory.select() + .from(l) + .where(l.getId().eq(id)) + .fetchCount(); + break; + + case OBSERVATION: + AbstractQObservations o = qCollection.qObservations; + count = qFactory.select() + .from(o) + .where(o.getId().eq(id)) + .fetchCount(); + break; + + case OBSERVEDPROPERTY: + AbstractQObsProperties op = qCollection.qObsProperties; + count = qFactory.select() + .from(op) + .where(op.getId().eq(id)) + .fetchCount(); + break; + + case SENSOR: + AbstractQSensors s = qCollection.qSensors; + count = qFactory.select() + .from(s) + .where(s.getId().eq(id)) + .fetchCount(); + break; + + case THING: + AbstractQThings t = qCollection.qThings; + count = qFactory.select() + .from(t) + .where(t.getId().eq(id)) + .fetchCount(); + break; + + default: + throw new AssertionError(e.getEntityType().name()); + } + if (count > 1) { + LOGGER.error("More than one instance of {} with id {}.", e.getEntityType(), id); + } + return count > 0; + } + + public boolean entityExists(PostgresPersistenceManager pm, ResourcePath path) { + long count = pm.count(path, null); + if (count > 1) { + LOGGER.error("More than one instance of {}", path); + } + return count > 0; + } + + public static T insertTimeValue(T clause, DateTimePath startPath, DateTimePath endPath, TimeValue time) { + if (time instanceof TimeInstant) { + TimeInstant timeInstant = (TimeInstant) time; + insertTimeInstant(clause, endPath, timeInstant); + return insertTimeInstant(clause, startPath, timeInstant); + } else if (time instanceof TimeInterval) { + TimeInterval timeInterval = (TimeInterval) time; + return insertTimeInterval(clause, startPath, endPath, timeInterval); + } + return clause; + } + + public static T insertTimeInstant(T clause, DateTimePath path, TimeInstant time) { + if (time == null) { + return clause; + } + clause.set(path, new Timestamp(time.getDateTime().getMillis())); + return clause; + } + + public static T insertTimeInterval(T clause, DateTimePath startPath, DateTimePath endPath, TimeInterval time) { + if (time == null) { + return clause; + } + Interval interval = time.getInterval(); + clause.set(startPath, new Timestamp(interval.getStartMillis())); + clause.set(endPath, new Timestamp(interval.getEndMillis())); + return clause; + } + + /** + * Sets both the geometry and location in the clause. + * + * @param The type of the clause. + * @param clause The insert or update clause to add to. + * @param locationPath The path to the location column. + * @param geomPath The path to the geometry column. + * @param encodingType The encoding type. + * @param location The location. + * @return The insert or update clause. + */ + public static T insertGeometry(T clause, StringPath locationPath, GeometryPath geomPath, String encodingType, final Object location) { + if (encodingType != null && GeoJsonDeserializier.ENCODINGS.contains(encodingType.toLowerCase())) { + insertGeometryKnownEncoding(location, clause, geomPath, locationPath); + } else { + String json; + json = objectToJson(location); + clause.setNull(geomPath); + clause.set(locationPath, json); + } + return clause; + } + + private static void insertGeometryKnownEncoding(final Object location, T clause, GeometryPath geomPath, StringPath locationPath) { + String locJson; + try { + locJson = new GeoJsonSerializer().serialize(location); + } catch (JsonProcessingException ex) { + LOGGER.error("Failed to store.", ex); + throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); + } + + // Postgres does not support Feature. + Object geoLocation = location; + if (location instanceof Feature) { + geoLocation = ((Feature) location).getGeometry(); + } + // Ensure the geoJson has a crs, otherwise Postgres complains. + if (geoLocation instanceof GeoJsonObject) { + GeoJsonObject geoJsonObject = (GeoJsonObject) geoLocation; + Crs crs = geoJsonObject.getCrs(); + if (crs == null) { + crs = new Crs(); + crs.setType(CrsType.name); + crs.getProperties().put("name", "EPSG:4326"); + geoJsonObject.setCrs(crs); + } + } + String geoJson; + try { + geoJson = new GeoJsonSerializer().serialize(geoLocation); + } catch (JsonProcessingException ex) { + LOGGER.error("Failed to store.", ex); + throw new IllegalArgumentException("encoding specifies geoJson, but location not parsable as such."); + } + + try { + // geojson.jackson allows invalid polygons, geolatte catches those. + new JsonMapper().fromJson(geoJson, Geometry.class); + } catch (JsonException ex) { + throw new IllegalArgumentException("Invalid geoJson: " + ex.getMessage()); + } + clause.set(geomPath, Expressions.template(Geometry.class, "ST_Force2D(ST_Transform(ST_GeomFromGeoJSON({0}), 4326))", geoJson)); + clause.set(locationPath, locJson); + } + + public static Object reParseGeometry(String encodingType, Object object) { + String json = objectToJson(object); + return Utils.locationFromEncoding(encodingType, json); + } + + public static String objectToJson(Object object) { + if (object == null) { + return null; + } + try { + return getFormatter().writeValueAsString(object); + } catch (IOException ex) { + throw new IllegalStateException("Could not serialise object.", ex); + } + } + + public static ObjectMapper getFormatter() { + if (formatter == null) { + formatter = EntityParser.getSimpleObjectMapper(); + } + return formatter; + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/IdGenerationHandler.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/IdGenerationHandler.java new file mode 100644 index 000000000..26b02ae82 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/IdGenerationHandler.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * Copyright (C) 2018 KIT TECO, Vincenz-Prießnitz-Str. 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.sta.persistence.postgres; + +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * Class for handling the persistence setting "idGenerationMode". + * + * @author koepke + */ +public abstract class IdGenerationHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(IdGenerationHandler.class); + + /** + * The possible id generation modes. + */ + private enum IdGenerationType { + SERVER_GENERATED_ONLY, + SERVER_AND_CLIENT_GENERATED, + CLIENT_GENERATED_ONLY; + + private static final Map aliases = new HashMap<>(); + + public static IdGenerationType findType(String input) { + if (aliases.isEmpty()) { + init(); + } + return aliases.get(input.toLowerCase()); + } + + private static void init() { + for (IdGenerationType type : IdGenerationType.values()) { + aliases.put(type.name(), type); + aliases.put(type.name().replace("_", "").toLowerCase(), type); + } + } + } + + private static IdGenerationType idGenerationMode = IdGenerationType.SERVER_GENERATED_ONLY; + + private final Entity entity; + + /** + * Constructor for IdGenerationHandler. + * + * @param entity Entity for which idGenerationMode should be + * checked/applied. + */ + public IdGenerationHandler(Entity entity) { + this.entity = entity; + } + + /** + * + * Sets the idGenerationMode for all IdGenerationHandler instances. + * + * @param mode String that contains the idGenerationMode. + * @throws IllegalArgumentException Will be thrown if given idGenerationMode + * is not supported. + */ + public static void setIdGenerationMode(String mode) { + idGenerationMode = IdGenerationType.findType(mode); + if (idGenerationMode == null) { + // not a valid generation mode + String error = "Unknown idGenerationMode: " + mode + "."; + LOGGER.error(error); + throw new IllegalArgumentException(error); + } + } + + protected Entity getEntity() { + return entity; + } + + /** + * + * Returns the idvalue of the entity, which was used to create the instance + * of IdGeneration Handler. + * + * @return Value of the entity id. Can be null. + */ + public Object getIdValue() { + if (entity.getId() == null) { + return null; + } else { + // keep null pointer for error handling + return entity.getId().getValue(); + } + } + + /** + * + * Modify the entity id. + * + */ + public abstract void modifyClientSuppliedId(); + + /** + * + * Checks if a client generated id can/should be used with respect to the + * idGenerationMode. + * + * @return true if a valid client id can be used. + * @throws IncompleteEntityException Will be thrown if @iot.id is missing + * for client generated ids. + * @throws IllegalArgumentException Will be thrown if idGenerationMode is + * not supported. + */ + public boolean useClientSuppliedId() throws IncompleteEntityException { + switch (idGenerationMode) { + case SERVER_GENERATED_ONLY: + if (getIdValue() == null) { + LOGGER.trace("Using server generated id."); + return false; + } else { + LOGGER.warn("idGenerationMode is '{}' but @iot.id '{}' is present. Ignoring @iot.id.", idGenerationMode, getIdValue()); + return false; + } + + case SERVER_AND_CLIENT_GENERATED: + if (!validateClientSuppliedId()) { + LOGGER.debug("No valid @iot.id. Using server generated id."); + return false; + } + break; + + case CLIENT_GENERATED_ONLY: + if (!validateClientSuppliedId()) { + LOGGER.error("No @iot.id and idGenerationMode is '{}'", idGenerationMode); + throw new IncompleteEntityException("Error: no @iot.id"); + } + break; + + default: + // not a valid generation mode + LOGGER.error("idGenerationMode '{}' is not implemented.", idGenerationMode); + throw new IllegalArgumentException("idGenerationMode '" + idGenerationMode.toString() + "' is not implemented."); + } + + LOGGER.info("Using client generated id."); + return true; + } + + /** + * + * Checks if a client generated id is valid. + * + * @return true if client generated id is valid. + */ + protected abstract boolean validateClientSuppliedId(); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PathSqlBuilder.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PathSqlBuilder.java index 6aaceeb60..ca1dd616c 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PathSqlBuilder.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PathSqlBuilder.java @@ -20,10 +20,11 @@ import com.querydsl.core.Tuple; import com.querydsl.core.types.Expression; import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.ComparableExpressionBase; import com.querydsl.sql.RelationalPathBase; import com.querydsl.sql.SQLQuery; import com.querydsl.sql.SQLQueryFactory; -import de.fraunhofer.iosb.ilt.sta.model.id.Id; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; import de.fraunhofer.iosb.ilt.sta.path.EntityType; import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; @@ -38,22 +39,72 @@ */ public interface PathSqlBuilder extends ResourcePathVisitor { - public interface TableRef { + /** + * A class that keeps track of the latest table that was joined. + * + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ + public static class TableRef & Path, J extends Comparable> { - public EntityType getType(); + private EntityType type; + private RelationalPathBase qPath; + private I idPath; - public RelationalPathBase getqPath(); + public TableRef() { + } - public void clear(); + public TableRef(TableRef source) { + type = source.type; + qPath = source.qPath; + idPath = source.idPath; + } - public TableRef copy(); + public EntityType getType() { + return type; + } + + public void setType(EntityType type) { + this.type = type; + } + + public void clear() { + type = null; + qPath = null; + idPath = null; + } + + public TableRef copy() { + return new TableRef(this); + } + + public boolean isEmpty() { + return type == null && qPath == null; + } + + public RelationalPathBase getqPath() { + return qPath; + } + + public void setqPath(RelationalPathBase qPath) { + this.qPath = qPath; + } + + public I getIdPath() { + return idPath; + } + + public void setIdPath(I idPath) { + this.idPath = idPath; + } - public boolean isEmpty(); } + public SQLQuery buildFor(EntityType entityType, Id id, SQLQueryFactory sqlQueryFactory, PersistenceSettings settings); + public SQLQuery buildFor(ResourcePath path, Query query, SQLQueryFactory sqlQueryFactory, PersistenceSettings settings); public void queryEntityType(EntityType type, Id id, TableRef last); - public Map> expressionsForProperty(EntityProperty property, Path qPath, Map> target); + public Map expressionsForProperty(EntityProperty property, Path qPath, Map target); } diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PathSqlBuilderImp.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PathSqlBuilderImp.java new file mode 100644 index 000000000..ac15b11fa --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PathSqlBuilderImp.java @@ -0,0 +1,704 @@ +/* + * Copyright (C) 2016 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.sta.persistence.postgres; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.SubQueryExpression; +import com.querydsl.core.types.dsl.ComparableExpressionBase; +import com.querydsl.sql.SQLQuery; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLDeleteClause; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyArrayIndex; +import de.fraunhofer.iosb.ilt.sta.path.CustomPropertyPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.path.PropertyPathElement; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQFeatures; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocationsHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreamsObsProperties; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObsProperties; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQSensors; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThings; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Expand; +import de.fraunhofer.iosb.ilt.sta.query.OrderBy; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.settings.PersistenceSettings; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Builds a path for a query. Should not be re-used. + * + * @author scf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class PathSqlBuilderImp & Path, J extends Comparable> implements PathSqlBuilder { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(PathSqlBuilderImp.class); + private static final String DO_NOT_KNOW_HOW_TO_JOIN = "Do not know how to join"; + /** + * The prefix used for table aliases. The main entity is always + * <PREFIX>1. + */ + public static final String ALIAS_PREFIX = "e"; + + private final PropertyResolver propertyResolver; + private final QCollection qCollection; + + private SQLQuery sqlQuery; + private Set selectedProperties; + private final TableRef lastPath = new TableRef<>(); + private TableRef mainTable; + private int aliasNr = 0; + private boolean isFilter = false; + private boolean needsDistinct = false; + + public PathSqlBuilderImp(PropertyResolver propertyResolver) { + this.propertyResolver = propertyResolver; + this.qCollection = propertyResolver.qCollection; + } + + @Override + public synchronized SQLQuery buildFor(EntityType entityType, Id id, SQLQueryFactory sqlQueryFactory, PersistenceSettings settings) { + selectedProperties = Collections.emptySet(); + sqlQuery = sqlQueryFactory.select(); + lastPath.clear(); + aliasNr = 0; + queryEntityType(entityType, id, lastPath); + return sqlQuery; + } + + @Override + public synchronized SQLQuery buildFor(ResourcePath path, Query query, SQLQueryFactory sqlQueryFactory, PersistenceSettings settings) { + findSelectedProperties(query); + + sqlQuery = sqlQueryFactory.select(); + lastPath.clear(); + aliasNr = 0; + + int count = path.size(); + for (int i = count - 1; i >= 0; i--) { + ResourcePathElement element = path.get(i); + element.visit(this); + } + + addOrderAndFilter(query, settings); + + return sqlQuery; + } + + private void findSelectedProperties(Query query) { + selectedProperties = new HashSet<>(); + if (query == null) { + return; + } + for (Property property : query.getSelect()) { + selectedProperties.add(property); + } + if (!query.getExpand().isEmpty() && !selectedProperties.isEmpty()) { + // If we expand, and there is a $select, make sure we load the ID and the navigation properties. + // If no $select, then we already load everything. + selectedProperties.add(EntityProperty.ID); + for (Expand expand : query.getExpand()) { + List expandPath = expand.getPath(); + if (!expandPath.isEmpty()) { + selectedProperties.add(expandPath.get(0)); + } + } + } + } + + private void addOrderAndFilter(Query query, PersistenceSettings settings) { + if (query != null) { + PgExpressionHandler handler = new PgExpressionHandler(this, mainTable.copy()); + for (OrderBy ob : query.getOrderBy()) { + handler.addOrderbyToQuery(ob, sqlQuery); + } + isFilter = true; + de.fraunhofer.iosb.ilt.sta.query.expression.Expression filter = query.getFilter(); + if (filter != null) { + handler.addFilterToQuery(filter, sqlQuery); + } + if (settings.getAlwaysOrderbyId()) { + sqlQuery.orderBy(mainTable.getIdPath().asc()); + } + if (needsDistinct) { + sqlQuery.distinct(); + } + } + } + + public SQLDeleteClause createDelete(EntitySetPathElement set, SQLQueryFactory sqlQueryFactory, SubQueryExpression idSelect) { + switch (set.getEntityType()) { + case DATASTREAM: + return sqlQueryFactory.delete(qCollection.qDatastreams).where(qCollection.qDatastreams.getId().in(idSelect)); + + case MULTIDATASTREAM: + return sqlQueryFactory.delete(qCollection.qMultiDatastreams).where(qCollection.qMultiDatastreams.getId().in(idSelect)); + + case FEATUREOFINTEREST: + return sqlQueryFactory.delete(qCollection.qFeatures).where(qCollection.qFeatures.getId().in(idSelect)); + + case HISTORICALLOCATION: + return sqlQueryFactory.delete(qCollection.qHistLocations).where(qCollection.qHistLocations.getId().in(idSelect)); + + case LOCATION: + return sqlQueryFactory.delete(qCollection.qLocations).where(qCollection.qLocations.getId().in(idSelect)); + + case OBSERVATION: + return sqlQueryFactory.delete(qCollection.qObservations).where(qCollection.qObservations.getId().in(idSelect)); + + case OBSERVEDPROPERTY: + return sqlQueryFactory.delete(qCollection.qObsProperties).where(qCollection.qObsProperties.getId().in(idSelect)); + + case SENSOR: + return sqlQueryFactory.delete(qCollection.qSensors).where(qCollection.qSensors.getId().in(idSelect)); + + case THING: + return sqlQueryFactory.delete(qCollection.qThings).where(qCollection.qThings.getId().in(idSelect)); + + default: + throw new AssertionError("Don't know how to delete" + set.getEntityType().name(), new IllegalArgumentException("Unknown type for delete")); + } + } + + @Override + public void visit(EntityPathElement element) { + queryEntityType(element.getEntityType(), element.getId(), lastPath); + } + + @Override + public void visit(EntitySetPathElement element) { + queryEntityType(element.getEntityType(), null, lastPath); + } + + @Override + public void visit(PropertyPathElement element) { + selectedProperties.add(element.getProperty()); + selectedProperties.add(EntityProperty.ID); + } + + @Override + public void visit(CustomPropertyPathElement element) { + // noting to do for custom properties. + } + + @Override + public void visit(CustomPropertyArrayIndex element) { + // noting to do for custom properties. + } + + @Override + public void queryEntityType(EntityType type, Id targetId, TableRef last) { + J id = null; + if (targetId != null) { + if (targetId.getBasicPersistenceType() != propertyResolver.getBasicPersistenceType()) { + throw new IllegalArgumentException("This implementation expects " + propertyResolver.getBasicPersistenceType() + " ids, not " + targetId.getBasicPersistenceType()); + } + id = (J) targetId.asBasicPersistenceType(); + } + + switch (type) { + case DATASTREAM: + queryDatastreams(id, last); + break; + + case MULTIDATASTREAM: + queryMultiDatastreams(id, last); + break; + + case FEATUREOFINTEREST: + queryFeatures(id, last); + break; + + case HISTORICALLOCATION: + queryHistLocations(id, last); + break; + + case LOCATION: + queryLocations(id, last); + break; + + case OBSERVATION: + queryObservations(id, last); + break; + + case OBSERVEDPROPERTY: + queryObsProperties(id, last); + break; + + case SENSOR: + querySensors(id, last); + break; + + case THING: + queryThings(id, last); + break; + + default: + LOGGER.error("Unknown entity type {}!?", type); + throw new IllegalStateException("Unknown entity type " + type); + } + if (mainTable == null && !last.isEmpty()) { + mainTable = new TableRef(last); + } + + } + + @Override + public Map expressionsForProperty(EntityProperty property, Path qPath, Map target) { + return propertyResolver.expressionsForProperty(property, qPath, target); + } + + private void queryDatastreams(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQDatastreams qDataStreams = qCollection.qDatastreams.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qDataStreams, selectedProperties)); + sqlQuery.from(qDataStreams); + } else { + switch (last.getType()) { + case THING: + AbstractQThings qThings = (AbstractQThings) last.getqPath(); + sqlQuery.innerJoin(qDataStreams).on(qDataStreams.getThingId().eq(qThings.getId())); + needsDistinct = true; + break; + + case OBSERVATION: + AbstractQObservations qObservations = (AbstractQObservations) last.getqPath(); + sqlQuery.innerJoin(qDataStreams).on(qDataStreams.getId().eq(qObservations.getDatastreamId())); + break; + + case SENSOR: + AbstractQSensors qSensors = (AbstractQSensors) last.getqPath(); + sqlQuery.innerJoin(qDataStreams).on(qDataStreams.getSensorId().eq(qSensors.getId())); + needsDistinct = true; + break; + + case OBSERVEDPROPERTY: + AbstractQObsProperties qObsProperties = (AbstractQObsProperties) last.getqPath(); + sqlQuery.innerJoin(qDataStreams).on(qDataStreams.getObsPropertyId().eq(qObsProperties.getId())); + needsDistinct = true; + break; + + case DATASTREAM: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto Datastreams.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.DATASTREAM); + last.setqPath(qDataStreams); + last.setIdPath(qDataStreams.getId()); + } + if (entityId != null) { + sqlQuery.where(qDataStreams.getId().eq(entityId)); + } + } + + private void queryMultiDatastreams(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQMultiDatastreams qMultiDataStreams = qCollection.qMultiDatastreams.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qMultiDataStreams, selectedProperties)); + sqlQuery.from(qMultiDataStreams); + } else { + switch (last.getType()) { + case THING: + AbstractQThings qThings = (AbstractQThings) last.getqPath(); + sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.getThingId().eq(qThings.getId())); + needsDistinct = true; + break; + + case OBSERVATION: + AbstractQObservations qObservations = (AbstractQObservations) last.getqPath(); + sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.getId().eq(qObservations.getMultiDatastreamId())); + break; + + case SENSOR: + AbstractQSensors qSensors = (AbstractQSensors) last.getqPath(); + sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.getSensorId().eq(qSensors.getId())); + needsDistinct = true; + break; + + case OBSERVEDPROPERTY: + AbstractQObsProperties qObsProperties = (AbstractQObsProperties) last.getqPath(); + AbstractQMultiDatastreamsObsProperties qMdOp = qCollection.qMultiDatastreamsObsProperties.newWithAlias(alias + "j1"); + sqlQuery.innerJoin(qMdOp).on(qObsProperties.getId().eq(qMdOp.getObsPropertyId())); + sqlQuery.innerJoin(qMultiDataStreams).on(qMultiDataStreams.getId().eq(qMdOp.getMultiDatastreamId())); + if (!isFilter) { + sqlQuery.orderBy(qMdOp.rank.asc()); + } else { + needsDistinct = true; + } + break; + + case MULTIDATASTREAM: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto Datastreams.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.MULTIDATASTREAM); + last.setqPath(qMultiDataStreams); + last.setIdPath(qMultiDataStreams.getId()); + } + if (entityId != null) { + sqlQuery.where(qMultiDataStreams.getId().eq(entityId)); + } + } + + private void queryThings(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQThings qThings = qCollection.qThings.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qThings, selectedProperties)); + sqlQuery.from(qThings); + } else { + switch (last.getType()) { + case DATASTREAM: + AbstractQDatastreams qDatastreams = (AbstractQDatastreams) last.getqPath(); + sqlQuery.innerJoin(qThings).on(qThings.getId().eq(qDatastreams.getThingId())); + break; + + case MULTIDATASTREAM: + AbstractQMultiDatastreams qMultiDatastreams = (AbstractQMultiDatastreams) last.getqPath(); + sqlQuery.innerJoin(qThings).on(qThings.getId().eq(qMultiDatastreams.getThingId())); + break; + + case HISTORICALLOCATION: + AbstractQHistLocations qHistLocations = (AbstractQHistLocations) last.getqPath(); + sqlQuery.innerJoin(qThings).on(qThings.getId().eq(qHistLocations.getThingId())); + break; + + case LOCATION: + AbstractQLocations qLocations = (AbstractQLocations) last.getqPath(); + AbstractQThingsLocations qTL = qCollection.qThingsLocations.newWithAlias(alias + "j1"); + sqlQuery.innerJoin(qTL).on(qLocations.getId().eq(qTL.getLocationId())); + sqlQuery.innerJoin(qThings).on(qThings.getId().eq(qTL.getThingId())); + needsDistinct = true; + break; + + case THING: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto Things.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.THING); + last.setqPath(qThings); + last.setIdPath(qThings.getId()); + } + if (entityId != null) { + sqlQuery.where(qThings.getId().eq(entityId)); + } + } + + private void queryFeatures(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQFeatures qFeatures = qCollection.qFeatures.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qFeatures, selectedProperties)); + sqlQuery.from(qFeatures); + } else { + switch (last.getType()) { + case OBSERVATION: + AbstractQObservations qObservations = (AbstractQObservations) last.getqPath(); + sqlQuery.innerJoin(qFeatures).on(qFeatures.getId().eq(qObservations.getFeatureId())); + break; + + case FEATUREOFINTEREST: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto Features.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.FEATUREOFINTEREST); + last.setqPath(qFeatures); + last.setIdPath(qFeatures.getId()); + } + if (entityId != null) { + sqlQuery.where(qFeatures.getId().eq(entityId)); + } + } + + private void queryHistLocations(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQHistLocations qHistLocations = qCollection.qHistLocations.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qHistLocations, selectedProperties)); + sqlQuery.from(qHistLocations); + } else { + switch (last.getType()) { + case THING: + AbstractQThings qThings = (AbstractQThings) last.getqPath(); + sqlQuery.innerJoin(qHistLocations).on(qThings.getId().eq(qHistLocations.getThingId())); + needsDistinct = true; + break; + + case LOCATION: + AbstractQLocations qLocations = (AbstractQLocations) last.getqPath(); + AbstractQLocationsHistLocations qLHL = qCollection.qLocationsHistLocations.newWithAlias(alias + "j1"); + sqlQuery.innerJoin(qLHL).on(qLocations.getId().eq(qLHL.getLocationId())); + sqlQuery.innerJoin(qHistLocations).on(qHistLocations.getId().eq(qLHL.getHistLocationId())); + needsDistinct = true; + break; + + case HISTORICALLOCATION: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto HistLocations.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.HISTORICALLOCATION); + last.setqPath(qHistLocations); + last.setIdPath(qHistLocations.getId()); + } + if (entityId != null) { + sqlQuery.where(qHistLocations.getId().eq(entityId)); + } + } + + private void queryLocations(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQLocations qLocations = qCollection.qLocations.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qLocations, selectedProperties)); + sqlQuery.from(qLocations); + } else { + switch (last.getType()) { + case THING: + AbstractQThings qThings = (AbstractQThings) last.getqPath(); + AbstractQThingsLocations qTL = qCollection.qThingsLocations.newWithAlias(alias + "j1"); + sqlQuery.innerJoin(qTL).on(qThings.getId().eq(qTL.getThingId())); + sqlQuery.innerJoin(qLocations).on(qLocations.getId().eq(qTL.getLocationId())); + needsDistinct = true; + break; + + case HISTORICALLOCATION: + AbstractQHistLocations qHistLocations = (AbstractQHistLocations) last.getqPath(); + AbstractQLocationsHistLocations qLHL = qCollection.qLocationsHistLocations.newWithAlias(alias + "j1"); + sqlQuery.innerJoin(qLHL).on(qHistLocations.getId().eq(qLHL.getHistLocationId())); + sqlQuery.innerJoin(qLocations).on(qLocations.getId().eq(qLHL.getLocationId())); + needsDistinct = true; + break; + + case LOCATION: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto Locations.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.LOCATION); + last.setqPath(qLocations); + last.setIdPath(qLocations.getId()); + } + if (entityId != null) { + sqlQuery.where(qLocations.getId().eq(entityId)); + } + } + + private void querySensors(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQSensors qSensors = qCollection.qSensors.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qSensors, selectedProperties)); + sqlQuery.from(qSensors); + } else { + switch (last.getType()) { + case DATASTREAM: + AbstractQDatastreams qDatastreams = (AbstractQDatastreams) last.getqPath(); + sqlQuery.innerJoin(qSensors).on(qSensors.getId().eq(qDatastreams.getSensorId())); + break; + + case MULTIDATASTREAM: + AbstractQMultiDatastreams qMultiDatastreams = (AbstractQMultiDatastreams) last.getqPath(); + sqlQuery.innerJoin(qSensors).on(qSensors.getId().eq(qMultiDatastreams.getSensorId())); + break; + + case SENSOR: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto Sensors.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.SENSOR); + last.setqPath(qSensors); + last.setIdPath(qSensors.getId()); + } + if (entityId != null) { + sqlQuery.where(qSensors.getId().eq(entityId)); + } + } + + private void queryObservations(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQObservations qObservations = qCollection.qObservations.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qObservations, selectedProperties)); + sqlQuery.from(qObservations); + } else { + switch (last.getType()) { + case FEATUREOFINTEREST: + AbstractQFeatures qFeatures = (AbstractQFeatures) last.getqPath(); + sqlQuery.innerJoin(qObservations).on(qFeatures.getId().eq(qObservations.getFeatureId())); + needsDistinct = true; + break; + + case DATASTREAM: + AbstractQDatastreams qDatastreams = (AbstractQDatastreams) last.getqPath(); + sqlQuery.innerJoin(qObservations).on(qDatastreams.getId().eq(qObservations.getDatastreamId())); + needsDistinct = true; + break; + + case MULTIDATASTREAM: + AbstractQMultiDatastreams qMultiDatastreams = (AbstractQMultiDatastreams) last.getqPath(); + sqlQuery.innerJoin(qObservations).on(qMultiDatastreams.getId().eq(qObservations.getMultiDatastreamId())); + needsDistinct = true; + break; + + case OBSERVATION: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto Observations.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.OBSERVATION); + last.setqPath(qObservations); + last.setIdPath(qObservations.getId()); + } + if (entityId != null) { + sqlQuery.where(qObservations.getId().eq(entityId)); + } + } + + private void queryObsProperties(J entityId, TableRef last) { + int nr = ++aliasNr; + String alias = ALIAS_PREFIX + nr; + AbstractQObsProperties qObsProperties = qCollection.qObsProperties.newWithAlias(alias); + boolean added = true; + if (last.getType() == null) { + sqlQuery.select(propertyResolver.getExpressions(qObsProperties, selectedProperties)); + sqlQuery.from(qObsProperties); + } else { + switch (last.getType()) { + case MULTIDATASTREAM: + AbstractQMultiDatastreams qMultiDatastreams = (AbstractQMultiDatastreams) last.getqPath(); + AbstractQMultiDatastreamsObsProperties qMdOp = qCollection.qMultiDatastreamsObsProperties.newWithAlias(alias + "j1"); + sqlQuery.innerJoin(qMdOp).on(qMultiDatastreams.getId().eq(qMdOp.getMultiDatastreamId())); + sqlQuery.innerJoin(qObsProperties).on(qObsProperties.getId().eq(qMdOp.getObsPropertyId())); + needsDistinct = true; + break; + + case DATASTREAM: + AbstractQDatastreams qDatastreams = (AbstractQDatastreams) last.getqPath(); + sqlQuery.innerJoin(qObsProperties).on(qObsProperties.getId().eq(qDatastreams.getObsPropertyId())); + break; + case OBSERVEDPROPERTY: + added = false; + break; + + default: + LOGGER.error("Do not know how to join {} onto ObsProperties.", last.getType()); + throw new IllegalStateException(DO_NOT_KNOW_HOW_TO_JOIN); + } + } + if (added) { + last.setType(EntityType.OBSERVEDPROPERTY); + last.setqPath(qObsProperties); + last.setIdPath(qObsProperties.getId()); + } + if (entityId != null) { + sqlQuery.where(qObsProperties.getId().eq(entityId)); + } + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PgExpressionHandler.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PgExpressionHandler.java index 0413c132c..d3b667c01 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PgExpressionHandler.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PgExpressionHandler.java @@ -24,13 +24,12 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.ComparableExpression; import com.querydsl.core.types.dsl.ComparableExpressionBase; -import com.querydsl.core.types.dsl.DateExpression; import com.querydsl.core.types.dsl.DateTimeExpression; +import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.core.types.dsl.NumberPath; import com.querydsl.core.types.dsl.StringExpression; -import com.querydsl.core.types.dsl.TimeTemplate; import com.querydsl.spatial.GeometryExpression; import com.querydsl.sql.SQLExpressions; import com.querydsl.sql.SQLQuery; @@ -39,19 +38,19 @@ import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; import de.fraunhofer.iosb.ilt.sta.path.Property; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ConstantDateExpression; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ConstantDateTimeExpression; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ConstantDurationExpression; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ConstantGeometryExpression; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ConstantNumberExpression; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ConstantStringExpression; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ConstantTimeExpression; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.JsonExpressionFactory; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.ListExpression; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StaDateTimeExpression; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StaDurationExpression; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StaTimeIntervalExpression; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StaTimeIntervalExpression.KEY_TIME_INTERVAL_END; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StaTimeIntervalExpression.KEY_TIME_INTERVAL_START; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StringCastExpressionFactory; import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeExpression; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_END; -import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.TimeIntervalExpression.KEY_TIME_INTERVAL_START; import de.fraunhofer.iosb.ilt.sta.query.OrderBy; import de.fraunhofer.iosb.ilt.sta.query.expression.ExpressionVisitor; import de.fraunhofer.iosb.ilt.sta.query.expression.Path; @@ -92,15 +91,15 @@ import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.Time; import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.TotalOffsetMinutes; import de.fraunhofer.iosb.ilt.sta.query.expression.function.date.Year; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoDistance; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoIntersects; -import de.fraunhofer.iosb.ilt.sta.query.expression.function.geospatial.GeoLength; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.And; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.Not; import de.fraunhofer.iosb.ilt.sta.query.expression.function.logical.Or; import de.fraunhofer.iosb.ilt.sta.query.expression.function.math.Ceiling; import de.fraunhofer.iosb.ilt.sta.query.expression.function.math.Floor; import de.fraunhofer.iosb.ilt.sta.query.expression.function.math.Round; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoDistance; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoIntersects; +import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.GeoLength; import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.STContains; import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.STCrosses; import de.fraunhofer.iosb.ilt.sta.query.expression.function.spatialrelation.STDisjoint; @@ -136,6 +135,7 @@ import java.util.List; import java.util.Map; import java.util.TimeZone; +import org.geojson.GeoJsonObject; import org.geolatte.geom.Geometry; import org.geolatte.geom.codec.Wkt; import org.joda.time.DateTime; @@ -150,7 +150,7 @@ * * @author Hylke van der Schaaf */ -public class PgExpressionHandler implements ExpressionVisitor> { +public class PgExpressionHandler implements ExpressionVisitor { /** * The logger for this class. @@ -168,14 +168,14 @@ public PgExpressionHandler(PathSqlBuilder psb, PathSqlBuilder.TableRef tableRef) } public void addFilterToQuery(de.fraunhofer.iosb.ilt.sta.query.expression.Expression filter, SQLQuery sqlQuery) { - Expression filterExpression = filter.accept(this); + Expression filterExpression = filter.accept(this); if (filterExpression instanceof Predicate) { Predicate predicate = (Predicate) filterExpression; sqlQuery.where(predicate); return; } else if (filterExpression instanceof ListExpression) { ListExpression listExpression = (ListExpression) filterExpression; - for (Expression expression : listExpression.getExpressions().values()) { + for (Expression expression : listExpression.getExpressions().values()) { if (expression instanceof Predicate) { Predicate predicate = (Predicate) expression; sqlQuery.where(predicate); @@ -188,18 +188,22 @@ public void addFilterToQuery(de.fraunhofer.iosb.ilt.sta.query.expression.Express } public void addOrderbyToQuery(OrderBy orderBy, SQLQuery sqlQuery) { - Expression resultExpression = orderBy.getExpression().accept(this); - if (resultExpression instanceof TimeIntervalExpression) { - TimeIntervalExpression ti = (TimeIntervalExpression) resultExpression; + Expression resultExpression = orderBy.getExpression().accept(this); + if (resultExpression instanceof StaTimeIntervalExpression) { + StaTimeIntervalExpression ti = (StaTimeIntervalExpression) resultExpression; addToQuery(orderBy, ti.getStart(), sqlQuery); addToQuery(orderBy, ti.getEnd(), sqlQuery); } - if (resultExpression instanceof ConstantDurationExpression) { - ConstantDurationExpression duration = (ConstantDurationExpression) resultExpression; + if (resultExpression instanceof StaDurationExpression) { + StaDurationExpression duration = (StaDurationExpression) resultExpression; addToQuery(orderBy, duration.getDuration(), sqlQuery); } + if (resultExpression instanceof StaDateTimeExpression) { + StaDateTimeExpression dateTime = (StaDateTimeExpression) resultExpression; + addToQuery(orderBy, dateTime.getDateTime(), sqlQuery); + } if (resultExpression instanceof ListExpression) { - for (Expression sqlExpression : ((ListExpression) resultExpression).getExpressionsForOrder().values()) { + for (Expression sqlExpression : ((ListExpression) resultExpression).getExpressionsForOrder().values()) { addToQuery(orderBy, sqlExpression, sqlQuery); } } else { @@ -218,7 +222,7 @@ public void addToQuery(OrderBy orderBy, Expression sqlExpression, SQLQuery> T checkType(Class expectedClazz, Expr } public static > T getSingleOfType(Class expectedClazz, Expression input) { - if (input instanceof TimeIntervalExpression) { - TimeIntervalExpression ti = (TimeIntervalExpression) input; - return checkType(expectedClazz, ti.getStart(), true); - } if (input instanceof ListExpression) { ListExpression listExpression = (ListExpression) input; - Map> expressions = listExpression.getExpressions(); - Collection> values = expressions.values(); + Map expressions = listExpression.getExpressions(); + Collection values = expressions.values(); // Two passes, first do an exact check (no casting allowed) for (Expression subResult : values) { try { @@ -273,83 +273,112 @@ public static > T getSingleOfType(Class expectedClazz } } + private static class PathState { + + PathSqlBuilder.TableRef pathTableRef; + List elements; + Expression finalExpression = null; + int curIndex; + boolean finished = false; + } + @Override public Expression visit(Path path) { - PathSqlBuilder.TableRef pathTableRef = tableRef.copy(); - List elements = path.getElements(); - Expression finalExpression = null; - for (int i = 0; i < elements.size(); i++) { - Property element = elements.get(i); + PathState state = new PathState(); + state.pathTableRef = tableRef.copy(); + state.elements = path.getElements(); + for (state.curIndex = 0; state.curIndex < state.elements.size() && !state.finished; state.curIndex++) { + Property element = state.elements.get(state.curIndex); if (element instanceof CustomProperty) { - if (finalExpression == null) { - throw new IllegalArgumentException("CustomProperty must follow an EntityProperty: " + path); - } - // generate finalExpression::jsonb#>>'{x,y,z}' - JsonExpressionFactory jsonFactory = new JsonExpressionFactory(finalExpression); - for (; i < elements.size(); i++) { - jsonFactory.addToPath(elements.get(i).getName()); - } - return jsonFactory.build(); + handleCustomProperty(state, path); + } else if (element instanceof EntityProperty) { - if (finalExpression != null) { - throw new IllegalArgumentException("EntityProperty can not follow an other EntityProperty: " + path); - } - EntityProperty entityProperty = (EntityProperty) element; - Map> pathExpressions = psb.expressionsForProperty(entityProperty, pathTableRef.getqPath(), new LinkedHashMap<>()); - if (pathExpressions.size() == 1) { - finalExpression = pathExpressions.values().iterator().next(); - } else { - finalExpression = getSubExpression(elements, i, pathExpressions); - } + handleEntityProperty(state, path, element); + } else if (element instanceof NavigationProperty) { - if (finalExpression != null) { - throw new IllegalArgumentException("NavigationProperty can not follow an EntityProperty: " + path); - } - NavigationProperty navigationProperty = (NavigationProperty) element; - psb.queryEntityType(navigationProperty.getType(), null, pathTableRef); + handleNavigationProperty(state, path, element); } } - if (finalExpression == null) { + if (state.finalExpression == null) { throw new IllegalArgumentException("Path does not end in an EntityProperty: " + path); } - return finalExpression; + if (state.finalExpression instanceof DateTimePath) { + DateTimePath dateTimePath = (DateTimePath) state.finalExpression; + state.finalExpression = new StaDateTimeExpression(dateTimePath); + } + return state.finalExpression; + } + + private void handleCustomProperty(PathState state, Path path) { + if (state.finalExpression == null) { + throw new IllegalArgumentException("CustomProperty must follow an EntityProperty: " + path); + } + // generate finalExpression::jsonb#>>'{x,y,z}' + JsonExpressionFactory jsonFactory = new JsonExpressionFactory(state.finalExpression); + for (; state.curIndex < state.elements.size(); state.curIndex++) { + jsonFactory.addToPath(state.elements.get(state.curIndex).getName()); + } + state.finalExpression = jsonFactory.build(); + state.finished = true; + } + + private void handleEntityProperty(PathState state, Path path, Property element) { + if (state.finalExpression != null) { + throw new IllegalArgumentException("EntityProperty can not follow an other EntityProperty: " + path); + } + EntityProperty entityProperty = (EntityProperty) element; + Map pathExpressions = psb.expressionsForProperty(entityProperty, state.pathTableRef.getqPath(), new LinkedHashMap<>()); + if (pathExpressions.size() == 1) { + state.finalExpression = pathExpressions.values().iterator().next(); + } else { + state.finalExpression = getSubExpression(state, pathExpressions); + } + } + + private void handleNavigationProperty(PathState state, Path path, Property element) { + if (state.finalExpression != null) { + throw new IllegalArgumentException("NavigationProperty can not follow an EntityProperty: " + path); + } + NavigationProperty navigationProperty = (NavigationProperty) element; + psb.queryEntityType(navigationProperty.getType(), null, state.pathTableRef); } - private Expression getSubExpression(List elements, int curIdx, Map> pathExpressions) { - int nextIdx = curIdx + 1; - if (elements.size() > nextIdx) { - Property subProperty = elements.get(nextIdx); + private Expression getSubExpression(PathState state, Map pathExpressions) { + int nextIdx = state.curIndex + 1; + if (state.elements.size() > nextIdx) { + Property subProperty = state.elements.get(nextIdx); // If the subProperty is unknown, and the expression can be of type JSON, // then we assume JSON. if (!pathExpressions.containsKey(subProperty.getName()) && pathExpressions.containsKey("j")) { return pathExpressions.get("j"); } - // We can not accept json, so the subProperty must a known name. - // We consume the subProperty. If the pathExpressions contains the property, there is no need to look further. - elements.remove(nextIdx); + // We can not accept json, so the subProperty must be a known direction. + state.finished = true; return pathExpressions.get(subProperty.getName()); } else { if (pathExpressions.containsKey(KEY_TIME_INTERVAL_START) && pathExpressions.containsKey(KEY_TIME_INTERVAL_END)) { - return new TimeIntervalExpression(pathExpressions); + return new StaTimeIntervalExpression(pathExpressions); } return new ListExpression(pathExpressions); } } - public Expression[] findPair(Expression p1, Expression p2) { + public Expression[] findPair(Expression p1, Expression p2) { Expression[] result = new Expression[2]; try { result[0] = getSingleOfType(NumberExpression.class, p1); result[1] = getSingleOfType(NumberExpression.class, p2); return result; } catch (IllegalArgumentException e) { + // Not of the requested type. } try { result[0] = getSingleOfType(BooleanExpression.class, p1); result[1] = getSingleOfType(BooleanExpression.class, p2); return result; } catch (IllegalArgumentException e) { + // Not of the requested type. } // If both are strings, use strings. boolean firstIsString = false; @@ -359,6 +388,7 @@ public Expression[] findPair(Expression p1, Expression p2) { result[1] = getSingleOfType(StringExpression.class, p2); return result; } catch (IllegalArgumentException e) { + // Not of the requested type. } // If one of the two is a string, cast the other if (firstIsString) { @@ -370,6 +400,7 @@ public Expression[] findPair(Expression p1, Expression p2) { result[0] = StringCastExpressionFactory.build(p1); return result; } catch (IllegalArgumentException e) { + // Not of the requested type. } } @@ -388,15 +419,14 @@ public Expression visit(DateConstant node) { LocalDate date = node.getValue(); Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); instance.set(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth()); - ConstantDateExpression constant = new ConstantDateExpression(new java.sql.Date(instance.getTimeInMillis())); - return constant; + return new ConstantDateExpression(new java.sql.Date(instance.getTimeInMillis())); } @Override public Expression visit(DateTimeConstant node) { DateTime value = node.getValue(); DateTimeZone zone = value.getZone(); - return new ConstantDateTimeExpression(new Timestamp(value.getMillis()), zone == DateTimeZone.UTC); + return new StaDateTimeExpression(new Timestamp(value.getMillis()), zone == DateTimeZone.UTC); } @Override @@ -406,26 +436,21 @@ public Expression visit(DoubleConstant node) { @Override public Expression visit(DurationConstant node) { - return new ConstantDurationExpression(node); + return new StaDurationExpression(node); } @Override public Expression visit(IntervalConstant node) { Interval value = node.getValue(); - return new TimeIntervalExpression( - new ConstantDateTimeExpression( - new Timestamp(value.getStartMillis()), true - ), - new ConstantDateTimeExpression( - new Timestamp(value.getEndMillis()), true - ) + return new StaTimeIntervalExpression( + new Timestamp(value.getStartMillis()), + new Timestamp(value.getEndMillis()) ); } @Override public Expression visit(IntegerConstant node) { - ConstantNumberExpression constant = new ConstantNumberExpression(node.getValue()); - return constant; + return new ConstantNumberExpression(node.getValue()); } @Override @@ -446,7 +471,7 @@ public Expression visit(PolygonConstant node) { return new ConstantGeometryExpression(geom); } - private Geometry fromGeoJsonConstant(GeoJsonConstant node) { + private Geometry fromGeoJsonConstant(GeoJsonConstant node) { if (node.getValue().getCrs() == null) { return Wkt.fromWkt("SRID=4326;" + node.getSource()); } @@ -455,8 +480,7 @@ private Geometry fromGeoJsonConstant(GeoJsonConstant node) { @Override public Expression visit(StringConstant node) { - ConstantStringExpression constant = new ConstantStringExpression(node.getValue()); - return constant; + return new ConstantStringExpression(node.getValue()); } @Override @@ -464,8 +488,7 @@ public Expression visit(TimeConstant node) { LocalTime time = node.getValue(); Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); instance.set(1970, 1, 1, time.getHourOfDay(), time.getMinuteOfHour(), time.getSecondOfMinute()); - ConstantTimeExpression constant = new ConstantTimeExpression(new java.sql.Time(instance.getTimeInMillis())); - return constant; + return new ConstantTimeExpression(new java.sql.Time(instance.getTimeInMillis())); } @Override @@ -473,16 +496,8 @@ public Expression visit(Before node) { List params = node.getParameters(); Expression p1 = params.get(0).accept(this); Expression p2 = params.get(1).accept(this); - if (p1 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti1 = (TimeIntervalExpression) p1; - return ti1.before(p2); - } - if (p2 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) p2; - return ti2.after(p1); - } - DateTimeExpression d1 = getSingleOfType(DateTimeExpression.class, p1); - DateTimeExpression d2 = getSingleOfType(DateTimeExpression.class, p2); + TimeExpression d1 = getSingleOfType(TimeExpression.class, p1); + TimeExpression d2 = getSingleOfType(TimeExpression.class, p2); return d1.before(d2); } @@ -491,16 +506,8 @@ public Expression visit(After node) { List params = node.getParameters(); Expression p1 = params.get(0).accept(this); Expression p2 = params.get(1).accept(this); - if (p1 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti1 = (TimeIntervalExpression) p1; - return ti1.after(p2); - } - if (p2 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) p2; - return ti2.before(p1); - } - DateTimeExpression d1 = getSingleOfType(DateTimeExpression.class, p1); - DateTimeExpression d2 = getSingleOfType(DateTimeExpression.class, p2); + TimeExpression d1 = getSingleOfType(TimeExpression.class, p1); + TimeExpression d2 = getSingleOfType(TimeExpression.class, p2); return d1.after(d2); } @@ -509,17 +516,9 @@ public Expression visit(Meets node) { List params = node.getParameters(); Expression p1 = params.get(0).accept(this); Expression p2 = params.get(1).accept(this); - if (p1 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti1 = (TimeIntervalExpression) p1; - return ti1.meets(p2); - } - if (p2 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) p2; - return ti2.meets(p1); - } - DateTimeExpression d1 = getSingleOfType(DateTimeExpression.class, p1); - DateTimeExpression d2 = getSingleOfType(DateTimeExpression.class, p2); - return d1.eq(d2); + TimeExpression d1 = getSingleOfType(TimeExpression.class, p1); + TimeExpression d2 = getSingleOfType(TimeExpression.class, p2); + return d1.meets(d2); } @Override @@ -527,8 +526,8 @@ public Expression visit(During node) { List params = node.getParameters(); Expression p1 = params.get(0).accept(this); Expression p2 = params.get(1).accept(this); - if (p2 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) p2; + if (p2 instanceof StaTimeIntervalExpression) { + StaTimeIntervalExpression ti2 = (StaTimeIntervalExpression) p2; return ti2.contains(p1); } else { throw new IllegalArgumentException("Second parameter of 'during' has to be an interval."); @@ -540,17 +539,9 @@ public Expression visit(Overlaps node) { List params = node.getParameters(); Expression p1 = params.get(0).accept(this); Expression p2 = params.get(1).accept(this); - if (p1 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti1 = (TimeIntervalExpression) p1; - return ti1.overlaps(p2); - } - if (p2 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) p2; - return ti2.overlaps(p1); - } - DateTimeExpression d1 = getSingleOfType(DateTimeExpression.class, p1); - DateTimeExpression d2 = getSingleOfType(DateTimeExpression.class, p2); - return d1.eq(d2); + TimeExpression d1 = getSingleOfType(TimeExpression.class, p1); + TimeExpression d2 = getSingleOfType(TimeExpression.class, p2); + return d1.overlaps(d2); } @Override @@ -558,17 +549,9 @@ public Expression visit(Starts node) { List params = node.getParameters(); Expression p1 = params.get(0).accept(this); Expression p2 = params.get(1).accept(this); - if (p1 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti1 = (TimeIntervalExpression) p1; - return ti1.starts(p2); - } - if (p2 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) p2; - return ti2.starts(p1); - } - DateTimeExpression d1 = getSingleOfType(DateTimeExpression.class, p1); - DateTimeExpression d2 = getSingleOfType(DateTimeExpression.class, p2); - return d1.eq(d2); + TimeExpression d1 = getSingleOfType(TimeExpression.class, p1); + TimeExpression d2 = getSingleOfType(TimeExpression.class, p2); + return d1.starts(d2); } @Override @@ -576,17 +559,9 @@ public Expression visit(Finishes node) { List params = node.getParameters(); Expression p1 = params.get(0).accept(this); Expression p2 = params.get(1).accept(this); - if (p1 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti1 = (TimeIntervalExpression) p1; - return ti1.finishes(p2); - } - if (p2 instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) p2; - return ti2.finishes(p1); - } - DateTimeExpression d1 = getSingleOfType(DateTimeExpression.class, p1); - DateTimeExpression d2 = getSingleOfType(DateTimeExpression.class, p2); - return d1.eq(d2); + TimeExpression d1 = getSingleOfType(TimeExpression.class, p1); + TimeExpression d2 = getSingleOfType(TimeExpression.class, p2); + return d1.finishes(d2); } @Override @@ -662,8 +637,7 @@ public Expression visit(Subtract node) { return ti1.sub(p2); } if (p2 instanceof TimeExpression) { - TimeExpression ti2 = (TimeExpression) p2; - return ti2.subi(p1); + throw new UnsupportedOperationException("Can not sub a time expression from a " + p1.getClass().getName()); } NumberExpression n1 = getSingleOfType(NumberExpression.class, p1); NumberExpression n2 = getSingleOfType(NumberExpression.class, p2); @@ -843,104 +817,98 @@ public Expression visit(NotEqual node) { public Expression visit(Date node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - DateExpression date = SQLExpressions.date(inExp); - return date; + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return SQLExpressions.date(inExp.getDateTime()); } @Override public Expression visit(Day node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - return inExp.dayOfMonth(); + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return inExp.getDateTime().dayOfMonth(); } @Override public Expression visit(FractionalSeconds node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - return inExp.milliSecond(); + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return inExp.getDateTime().milliSecond(); } @Override public Expression visit(Hour node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - return inExp.hour(); + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return inExp.getDateTime().hour(); } @Override public Expression visit(MaxDateTime node) { - return new ConstantDateTimeExpression(new Timestamp(PostgresPersistenceManager.DATETIME_MAX.getMillis()), true); + return new StaDateTimeExpression(new Timestamp(PostgresPersistenceManager.DATETIME_MAX.getMillis()), true); } @Override public Expression visit(MinDateTime node) { - return new ConstantDateTimeExpression(new Timestamp(PostgresPersistenceManager.DATETIME_MIN.getMillis()), true); + return new StaDateTimeExpression(new Timestamp(PostgresPersistenceManager.DATETIME_MIN.getMillis()), true); } @Override public Expression visit(Minute node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - return inExp.minute(); + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return inExp.getDateTime().minute(); } @Override public Expression visit(Month node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - return inExp.month(); + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return inExp.getDateTime().month(); } @Override public Expression visit(Now node) { - return DateTimeExpression.currentTimestamp(); + return new StaDateTimeExpression(DateTimeExpression.currentTimestamp()); } @Override public Expression visit(Second node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - return inExp.second(); + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return inExp.getDateTime().second(); } @Override public Expression visit(Time node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - if (inExp instanceof ConstantDateTimeExpression) { - ConstantDateTimeExpression constant = (ConstantDateTimeExpression) inExp; - if (!constant.isUtc()) { - throw new IllegalArgumentException("Constants passed to the time() function have to be in UTC."); - } + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + if (!inExp.isUtc()) { + throw new IllegalArgumentException("Constants passed to the time() function have to be in UTC."); } - TimeTemplate time = Expressions.timeTemplate(java.sql.Time.class, "pg_catalog.time({0})", inExp); - return time; + return Expressions.timeTemplate(java.sql.Time.class, "pg_catalog.time({0})", inExp.getDateTime()); } @Override public Expression visit(TotalOffsetMinutes node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - NumberExpression offset = Expressions.numberTemplate(Integer.class, "timezone({0})", inExp).divide(60); - return offset; + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return Expressions.numberTemplate(Integer.class, "timezone({0})", inExp.getDateTime()).divide(60); } @Override public Expression visit(Year node) { de.fraunhofer.iosb.ilt.sta.query.expression.Expression param = node.getParameters().get(0); Expression input = param.accept(this); - DateTimeExpression inExp = getSingleOfType(DateTimeExpression.class, input); - return inExp.year(); + TimeExpression inExp = getSingleOfType(TimeExpression.class, input); + return inExp.getDateTime().year(); } @Override diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PostgresPersistenceManager.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PostgresPersistenceManager.java index bc463374e..6c162924c 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PostgresPersistenceManager.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PostgresPersistenceManager.java @@ -17,21 +17,554 @@ */ package de.fraunhofer.iosb.ilt.sta.persistence.postgres; +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQuery; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.SQLTemplates; +import com.querydsl.sql.dml.SQLDeleteClause; +import com.querydsl.sql.spatial.PostGISTemplates; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.path.EntityPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePathElement; +import de.fraunhofer.iosb.ilt.sta.persistence.AbstractPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.factories.EntityFactory; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.sta.settings.Settings; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import de.fraunhofer.iosb.ilt.sta.util.UpgradeFailedException; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.inject.Provider; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import liquibase.Contexts; +import liquibase.Liquibase; +import liquibase.database.Database; +import liquibase.database.DatabaseFactory; +import liquibase.database.jvm.JdbcConnection; +import liquibase.exception.DatabaseException; +import liquibase.exception.LiquibaseException; +import liquibase.resource.ClassLoaderResourceAccessor; +import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.commons.dbcp2.ConnectionFactory; +import org.apache.commons.dbcp2.DriverManagerConnectionFactory; +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.dbcp2.PoolableConnectionFactory; +import org.apache.commons.dbcp2.PoolingDriver; +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPool; import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * * @author scf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. */ -public interface PostgresPersistenceManager { +public abstract class PostgresPersistenceManager & Path, J> extends AbstractPersistenceManager { - public static final String TAG_DATA_SOURCE = "db_jndi_datasource"; - public static final String TAG_DB_DRIVER = "db_driver"; - public static final String TAG_DB_URL = "db_url"; - public static final String TAG_DB_USERNAME = "db_username"; - public static final String TAG_DB_PASSWORD = "db_password"; + public static final String TAG_DATA_SOURCE = "db.jndi.datasource"; + public static final String TAG_DB_DRIVER = "db.driver"; + public static final String TAG_DB_URL = "db.url"; + public static final String TAG_DB_USERNAME = "db.username"; + public static final String TAG_DB_PASSWRD = "db.password"; + public static final String TAG_DB_MAXCONN = "db.conn.max"; + public static final String TAG_DB_MAXIDLE = "db.conn.idle.max"; + public static final String TAG_DB_MINIDLE = "db.conn.idle.min"; public static final DateTime DATETIME_MAX = DateTime.parse("9999-12-31T23:59:59.999Z"); public static final DateTime DATETIME_MIN = DateTime.parse("-4000-01-01T00:00:00.000Z"); + /** + * The logger for this class. + */ + static final Logger LOGGER = LoggerFactory.getLogger(PostgresPersistenceManager.class); + + static Map existingPools = new HashMap<>(); + + private static class MyConnectionWrapper implements Provider { + + private final CoreSettings settings; + private Connection connection; + + public MyConnectionWrapper(CoreSettings settings) { + this.settings = settings; + } + + @Override + public Connection get() { + if (connection == null) { + try { + connection = getConnection(settings); + } catch (SQLException ex) { + LOGGER.error("Could not inizialize " + getClass().getName(), ex); + } + } + return connection; + } + + protected boolean doCommit() { + if (connection == null) { + return true; + } + try { + if (!get().isClosed()) { + get().commit(); + return true; + } + } catch (SQLException ex) { + LOGGER.error("Exception rolling back.", ex); + } + return false; + } + + protected boolean doRollback() { + if (connection == null) { + return true; + } + try { + if (!get().isClosed()) { + LOGGER.debug("Rolling back changes."); + get().rollback(); + return true; + } + } catch (SQLException ex) { + LOGGER.error("Exception rolling back.", ex); + } + return false; + } + + protected boolean doClose() { + if (connection == null) { + return true; + } + try { + get().close(); + return true; + } catch (SQLException ex) { + LOGGER.error("Exception closing.", ex); + } finally { + clear(); + } + return false; + } + + public void clear() { + connection = null; + } + + } + + private CoreSettings settings; + private MyConnectionWrapper connectionProvider; + private SQLQueryFactory queryFactory; + + @Override + public void init(CoreSettings settings) { + this.settings = settings; + connectionProvider = new MyConnectionWrapper(settings); + } + + @Override + public CoreSettings getCoreSettings() { + return settings; + } + + public abstract EntityFactories getEntityFactories(); + + public abstract IdGenerationHandler createIdGenerationHanlder(Entity e); + + public SQLQueryFactory createQueryFactory() { + if (queryFactory == null) { + SQLTemplates templates = PostGISTemplates.builder().quote().build(); + queryFactory = new SQLQueryFactory(templates, connectionProvider); + } + return queryFactory; + } + + public abstract PropertyResolver getPropertyResolver(); + + public abstract String getLiquibaseChangelogFilename(); + + public long count(ResourcePath path, Query query) { + SQLQueryFactory qf = createQueryFactory(); + PathSqlBuilderImp psb = new PathSqlBuilderImp(getPropertyResolver()); + SQLQuery sqlQuery = psb.buildFor(path, query, qf, getCoreSettings().getPersistenceSettings()); + return sqlQuery.fetchCount(); + } + + @Override + public boolean validatePath(ResourcePath path) { + ResourcePathElement element = path.getIdentifiedElement(); + if (element == null) { + return true; + } + ResourcePath tempPath = new ResourcePath(); + while (element != null) { + tempPath.addPathElement(0, element); + element = element.getParent(); + } + return getEntityFactories().entityExists(this, tempPath); + } + + @Override + public Entity get(EntityType entityType, Id id) { + SQLQueryFactory qf = createQueryFactory(); + PathSqlBuilder psb = new PathSqlBuilderImp(getPropertyResolver()); + SQLQuery sqlQuery = psb.buildFor(entityType, id, qf, getCoreSettings().getPersistenceSettings()); + sqlQuery.limit(2); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Generated SQL:\n{}", sqlQuery.getSQL().getSQL()); + } + List results = sqlQuery.fetch(); + + EntityFactory factory; + factory = getEntityFactories().getFactoryFor(entityType); + return factory.create(results.get(0), null, new DataSize()); + } + + @Override + public Object get(ResourcePath path, Query query) { + ResourcePathElement lastElement = path.getLastElement(); + if (!(lastElement instanceof EntityPathElement) && !(lastElement instanceof EntitySetPathElement)) { + if (!query.getExpand().isEmpty()) { + LOGGER.warn("Expand only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); + query.getExpand().clear(); + } + if (!query.getSelect().isEmpty()) { + LOGGER.warn("Select only allowed on Entities or EntitySets. Not on {}!", lastElement.getClass()); + query.getSelect().clear(); + } + } + + SQLQueryFactory qf = createQueryFactory(); + PathSqlBuilderImp psb = new PathSqlBuilderImp(getPropertyResolver()); + SQLQuery sqlQuery = psb.buildFor(path, query, qf, getCoreSettings().getPersistenceSettings()); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Generated SQL:\n{}", sqlQuery.getSQL().getSQL()); + } + + EntityCreator entityCreator = new EntityCreator(this, path, query, sqlQuery); + lastElement.visit(entityCreator); + Object entity = entityCreator.getEntity(); + + if (path.isEntityProperty() && entity instanceof Map) { + Map map = (Map) entity; + if (map.get(entityCreator.getEntityName()) == null) { + return null; + } + } + if (path.isValue() && entity instanceof Map) { + Map map = (Map) entity; + entity = map.get(entityCreator.getEntityName()); + } + + return entity; + } + + @Override + public boolean doInsert(Entity entity) throws NoSuchEntityException, IncompleteEntityException { + EntityFactories ef = getEntityFactories(); + EntityFactory factory = ef.getFactoryFor(entity.getEntityType()); + factory.insert(this, entity); + return true; + } + + @Override + public EntityChangedMessage doUpdate(EntityPathElement pathElement, Entity entity) throws NoSuchEntityException, IncompleteEntityException { + EntityFactories ef = getEntityFactories(); + + entity.setId(pathElement.getId()); + J id = (J) pathElement.getId().getValue(); + if (!ef.entityExists(this, entity)) { + throw new NoSuchEntityException("No entity of type " + pathElement.getEntityType() + " with id " + id); + } + + EntityFactory factory = ef.getFactoryFor(entity.getEntityType()); + return factory.update(this, entity, id); + } + + @Override + public boolean doDelete(EntityPathElement pathElement) throws NoSuchEntityException { + EntityFactories ef = getEntityFactories(); + EntityType type = pathElement.getEntityType(); + EntityFactory factory = ef.getFactoryFor(type); + factory.delete(this, (J) pathElement.getId().getValue()); + return true; + } + + @Override + public void doDelete(ResourcePath path, Query query) { + query.setSelect(Arrays.asList(EntityProperty.ID)); + SQLQueryFactory qf = createQueryFactory(); + PathSqlBuilderImp psb = new PathSqlBuilderImp(getPropertyResolver()); + + SQLQuery sqlQuery = psb.buildFor(path, query, qf, getCoreSettings().getPersistenceSettings()); + SQLDeleteClause sqlDelete = psb.createDelete((EntitySetPathElement) path.getLastElement(), qf, sqlQuery); + + long rowCount = sqlDelete.execute(); + LOGGER.debug("Deleted {} rows using query {}", rowCount, sqlDelete); + } + + @Override + protected boolean doCommit() { + return connectionProvider.doCommit(); + } + + @Override + protected boolean doRollback() { + return connectionProvider.doRollback(); + } + + @Override + protected boolean doClose() { + return connectionProvider.doClose(); + } + + @Override + public String checkForUpgrades() { + StringWriter out = new StringWriter(); + try { + Connection connection = getConnection(getCoreSettings()); + + Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); + Liquibase liquibase = new liquibase.Liquibase(getLiquibaseChangelogFilename(), new ClassLoaderResourceAccessor(), database); + liquibase.update(new Contexts(), out); + database.commit(); + database.close(); + connection.close(); + + } catch (SQLException | DatabaseException ex) { + LOGGER.error("Could not initialise database.", ex); + out.append("Failed to initialise database:\n"); + out.append(ex.getLocalizedMessage()); + out.append("\n"); + } catch (LiquibaseException ex) { + LOGGER.error("Could not upgrade database.", ex); + out.append("Failed to upgrade database:\n"); + out.append(ex.getLocalizedMessage()); + out.append("\n"); + } + return out.toString(); + } + + @Override + public boolean doUpgrades(Writer out) throws UpgradeFailedException, IOException { + try { + Connection connection = getConnection(getCoreSettings()); + + Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); + Liquibase liquibase = new liquibase.Liquibase(getLiquibaseChangelogFilename(), new ClassLoaderResourceAccessor(), database); + liquibase.update(new Contexts()); + database.commit(); + database.close(); + connection.close(); + + } catch (SQLException | DatabaseException ex) { + LOGGER.error("Could not initialise database.", ex); + out.append("Failed to initialise database:\n"); + out.append(ex.getLocalizedMessage()); + out.append("\n"); + return false; + + } catch (LiquibaseException ex) { + out.append("Failed to upgrade database:\n"); + out.append(ex.getLocalizedMessage()); + out.append("\n"); + throw new UpgradeFailedException(ex); + } + return true; + } + + public static Connection getConnection(CoreSettings settings) throws SQLException { + Settings customSettings = settings.getPersistenceSettings().getCustomSettings(); + Connection connection = PostgresPersistenceManager.getPoolingConnection("FROST-Source", customSettings); + connection.setAutoCommit(false); + return connection; + } + + /** + * Creates a connection, setting up a new pool if needed. + * + * @param name The name to use for the source + * @param settings The settings, must contain the options for db driver, db + * url and username/password. + * @return A pooled database connection. + * @throws SQLException + */ + public static Connection getPoolingConnection(String name, Settings settings) throws SQLException { + ConnectionSource source = existingPools.get(name); + if (source == null) { + source = createPoolingConnection(name, settings); + } + return source.getConnection(); + } + + static ConnectionSource createPoolingConnection(String name, Settings settings) { + synchronized (existingPools) { + ConnectionSource source = existingPools.get(name); + if (source == null) { + if (settings.containsName(TAG_DB_URL) && !settings.get(TAG_DB_URL).isEmpty()) { + source = setupBasicDataSource(settings); + } else { + source = setupDataSource(settings); + } + existingPools.put(name, source); + } + return source; + } + } + + static ConnectionSource setupBasicDataSource(Settings settings) { + LOGGER.info("Setting up BasicDataSource for database connections."); + String driver = settings.getWithDefault(TAG_DB_DRIVER, "", String.class); + if (driver.isEmpty()) { + throw new IllegalArgumentException("Property '" + TAG_DB_DRIVER + "' must be non-empty"); + } + try { + Class.forName(settings.get(TAG_DB_DRIVER)); + BasicDataSource ds = new BasicDataSource(); + ds.setUrl(settings.get(TAG_DB_URL)); + ds.setUsername(settings.get(TAG_DB_USERNAME)); + ds.setPassword(settings.get(TAG_DB_PASSWRD)); + ds.setMaxIdle(settings.getInt(TAG_DB_MAXIDLE, ds.getMaxIdle())); + ds.setMaxTotal(settings.getInt(TAG_DB_MAXCONN, ds.getMaxTotal())); + ds.setMinIdle(settings.getInt(TAG_DB_MINIDLE, ds.getMinIdle())); + return new ConnectionSourceBasicDataSource(ds); + } catch (ClassNotFoundException exc) { + throw new IllegalArgumentException(exc); + } + } + + static ConnectionSource setupDataSource(Settings settings) { + LOGGER.info("Setting up DataSource for database connections."); + try { + String dataSourceName = settings.getWithDefault(TAG_DATA_SOURCE, "", String.class); + if (dataSourceName.isEmpty()) { + throw new IllegalArgumentException("Setting " + TAG_DATA_SOURCE + " must not be empty."); + } + InitialContext cxt = new InitialContext(); + DataSource ds = (DataSource) cxt.lookup("java:/comp/env/" + dataSourceName); + if (ds == null) { + throw new IllegalStateException("Data source not found!"); + } + return new ConnectionSourceDataSource(ds); + } catch (NamingException exc) { + throw new IllegalArgumentException("Failed to load context.", exc); + } + } + + static ConnectionSource setupDriverSource(String name, Settings settings) { + LOGGER.info("Setting up Driver for database connections."); + String driver = settings.getWithDefault(TAG_DB_DRIVER, "", String.class); + if (driver.isEmpty()) { + throw new IllegalArgumentException("Property '" + TAG_DB_DRIVER + "' must be non-empty"); + } + try { + Class.forName(settings.get(TAG_DB_DRIVER)); + setupPoolingDriver( + name, + settings.get(TAG_DB_URL), + settings.get(TAG_DB_USERNAME), + settings.get(TAG_DB_PASSWRD)); + } catch (ClassNotFoundException | SQLException exc) { + throw new IllegalArgumentException(exc); + } + return new ConnectionSourceDriverManager("jdbc:apache:commons:dbcp:" + name); + } + + /** + * Set up a connection pool. The driver used in the connection URI should + * already be loaded using + * Class.forName("org.apache.commons.dbcp2.PoolingDriver"); After calling + * this you can use "jdbc:apache:commons:dbcp:FROST-Pool" to connect. + * + * @param name The name of the pool to create. + * @param connectURI The URL of the database to connect to. + * @param username The username to use when connecting to the database. + * @param password The password to use when connecting to the database. + * @throws ClassNotFoundException If the PoolingDriver is not on the + * classpath. + * @throws SQLException If the dbcp driver could not be loaded. + */ + public static void setupPoolingDriver(String name, String connectURI, String username, String password) throws ClassNotFoundException, SQLException { + ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectURI, username, password); + PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, null); + ObjectPool connectionPool = new GenericObjectPool<>(poolableConnectionFactory); + poolableConnectionFactory.setPool(connectionPool); + Class.forName("org.apache.commons.dbcp2.PoolingDriver"); + PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:"); + driver.registerPool(name, connectionPool); + } + + static interface ConnectionSource { + + public Connection getConnection() throws SQLException; + } + + static class ConnectionSourceDataSource implements ConnectionSource { + + private final DataSource ds; + + public ConnectionSourceDataSource(DataSource ds) { + this.ds = ds; + } + + @Override + public Connection getConnection() throws SQLException { + return ds.getConnection(); + } + } + + static class ConnectionSourceDriverManager implements ConnectionSource { + + private final String name; + + public ConnectionSourceDriverManager(String name) { + this.name = name; + } + + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection(name); + } + + } + + static class ConnectionSourceBasicDataSource implements ConnectionSource { + + private final BasicDataSource dataSource; + + public ConnectionSourceBasicDataSource(BasicDataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + } + } diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PropertyResolver.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PropertyResolver.java new file mode 100644 index 000000000..803b8cb50 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/PropertyResolver.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2016 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.sta.persistence.postgres; + +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.BasicPersistenceType; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StaTimeIntervalExpression.KEY_TIME_INTERVAL_END; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.expression.StaTimeIntervalExpression.KEY_TIME_INTERVAL_START; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQFeatures; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObsProperties; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQSensors; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThings; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author scf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class PropertyResolver & Path, J> { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(PropertyResolver.class); + + private static interface ExpressionFactory { + + Expression get(T qPath); + } + + private final Map> epMapSingle = new HashMap<>(); + private final Map>> epMapMulti = new HashMap<>(); + private final Map> allForClass = new HashMap<>(); + + public final QCollection qCollection; + private final BasicPersistenceType basicPersistenceType; + + public PropertyResolver(EntityFactories entityFactories, BasicPersistenceType basicPersistenceType) { + this.qCollection = entityFactories.qCollection; + this.basicPersistenceType = basicPersistenceType; + init(); + } + + private void init() { + Class qDatastreamsClass = qCollection.qDatastreams.getClass(); + addEntry(EntityProperty.ID, qDatastreamsClass, (ExpressionFactory) AbstractQDatastreams::getId); + addEntry(EntityProperty.SELFLINK, qDatastreamsClass, (ExpressionFactory) AbstractQDatastreams::getId); + addEntry(EntityProperty.NAME, qDatastreamsClass, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.name); + addEntry(EntityProperty.DESCRIPTION, qDatastreamsClass, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.description); + addEntry(EntityProperty.OBSERVATIONTYPE, qDatastreamsClass, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.observationType); + addEntry(EntityProperty.OBSERVEDAREA, qDatastreamsClass, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.observedArea.asText()); + addEntry(EntityProperty.PHENOMENONTIME, qDatastreamsClass, KEY_TIME_INTERVAL_START, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.phenomenonTimeStart); + addEntry(EntityProperty.PHENOMENONTIME, qDatastreamsClass, KEY_TIME_INTERVAL_END, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.phenomenonTimeEnd); + addEntry(EntityProperty.PROPERTIES, qDatastreamsClass, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.properties); + addEntry(EntityProperty.RESULTTIME, qDatastreamsClass, KEY_TIME_INTERVAL_START, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.resultTimeStart); + addEntry(EntityProperty.RESULTTIME, qDatastreamsClass, KEY_TIME_INTERVAL_END, (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.resultTimeEnd); + addEntry(EntityProperty.UNITOFMEASUREMENT, qDatastreamsClass, "definition", (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.unitDefinition); + addEntry(EntityProperty.UNITOFMEASUREMENT, qDatastreamsClass, "name", (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.unitName); + addEntry(EntityProperty.UNITOFMEASUREMENT, qDatastreamsClass, "symbol", (ExpressionFactory) (AbstractQDatastreams qPath) -> qPath.unitSymbol); + addEntry(NavigationProperty.SENSOR, qDatastreamsClass, (ExpressionFactory) AbstractQDatastreams::getSensorId); + addEntry(NavigationProperty.OBSERVEDPROPERTY, qDatastreamsClass, (ExpressionFactory) AbstractQDatastreams::getObsPropertyId); + addEntry(NavigationProperty.THING, qDatastreamsClass, (ExpressionFactory) AbstractQDatastreams::getThingId); + + Class qMultiDatastreamsClass = qCollection.qMultiDatastreams.getClass(); + addEntry(EntityProperty.ID, qMultiDatastreamsClass, (ExpressionFactory) AbstractQMultiDatastreams::getId); + addEntry(EntityProperty.SELFLINK, qMultiDatastreamsClass, (ExpressionFactory) AbstractQMultiDatastreams::getId); + addEntry(EntityProperty.NAME, qMultiDatastreamsClass, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.name); + addEntry(EntityProperty.DESCRIPTION, qMultiDatastreamsClass, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.description); + addEntry(EntityProperty.MULTIOBSERVATIONDATATYPES, qMultiDatastreamsClass, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.observationTypes); + addEntry(EntityProperty.OBSERVEDAREA, qMultiDatastreamsClass, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.observedArea.asText()); + addEntry(EntityProperty.PHENOMENONTIME, qMultiDatastreamsClass, KEY_TIME_INTERVAL_START, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.phenomenonTimeStart); + addEntry(EntityProperty.PHENOMENONTIME, qMultiDatastreamsClass, KEY_TIME_INTERVAL_END, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.phenomenonTimeEnd); + addEntry(EntityProperty.PROPERTIES, qMultiDatastreamsClass, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.properties); + addEntry(EntityProperty.RESULTTIME, qMultiDatastreamsClass, KEY_TIME_INTERVAL_START, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.resultTimeStart); + addEntry(EntityProperty.RESULTTIME, qMultiDatastreamsClass, KEY_TIME_INTERVAL_END, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.resultTimeEnd); + addEntry(EntityProperty.UNITOFMEASUREMENTS, qMultiDatastreamsClass, (ExpressionFactory) (AbstractQMultiDatastreams qPath) -> qPath.unitOfMeasurements); + addEntry(NavigationProperty.SENSOR, qMultiDatastreamsClass, (ExpressionFactory) AbstractQMultiDatastreams::getSensorId); + addEntry(NavigationProperty.THING, qMultiDatastreamsClass, (ExpressionFactory) AbstractQMultiDatastreams::getThingId); + + Class qFeaturesClass = qCollection.qFeatures.getClass(); + addEntry(EntityProperty.ID, qFeaturesClass, (ExpressionFactory) AbstractQFeatures::getId); + addEntry(EntityProperty.SELFLINK, qFeaturesClass, (ExpressionFactory) AbstractQFeatures::getId); + addEntry(EntityProperty.NAME, qFeaturesClass, (ExpressionFactory) (AbstractQFeatures qPath) -> qPath.name); + addEntry(EntityProperty.DESCRIPTION, qFeaturesClass, (ExpressionFactory) (AbstractQFeatures qPath) -> qPath.description); + addEntry(EntityProperty.ENCODINGTYPE, qFeaturesClass, (ExpressionFactory) (AbstractQFeatures qPath) -> qPath.encodingType); + addEntry(EntityProperty.FEATURE, qFeaturesClass, "j", (ExpressionFactory) (AbstractQFeatures qPath) -> qPath.feature); + addEntry(EntityProperty.FEATURE, qFeaturesClass, "g", (ExpressionFactory) (AbstractQFeatures qPath) -> qPath.geom); + addEntry(EntityProperty.PROPERTIES, qFeaturesClass, (ExpressionFactory) (AbstractQFeatures qPath) -> qPath.properties); + + Class qHistLocationsClass = qCollection.qHistLocations.getClass(); + addEntry(EntityProperty.ID, qHistLocationsClass, (ExpressionFactory) AbstractQHistLocations::getId); + addEntry(EntityProperty.SELFLINK, qHistLocationsClass, (ExpressionFactory) AbstractQHistLocations::getId); + addEntry(EntityProperty.TIME, qHistLocationsClass, (ExpressionFactory) (AbstractQHistLocations qPath) -> qPath.time); + addEntry(NavigationProperty.THING, qHistLocationsClass, (ExpressionFactory) AbstractQHistLocations::getThingId); + + Class qLocationsClass = qCollection.qLocations.getClass(); + addEntry(EntityProperty.ID, qLocationsClass, (ExpressionFactory) AbstractQLocations::getId); + addEntry(EntityProperty.SELFLINK, qLocationsClass, (ExpressionFactory) AbstractQLocations::getId); + addEntry(EntityProperty.NAME, qLocationsClass, (ExpressionFactory) (AbstractQLocations qPath) -> qPath.name); + addEntry(EntityProperty.DESCRIPTION, qLocationsClass, (ExpressionFactory) (AbstractQLocations qPath) -> qPath.description); + addEntry(EntityProperty.ENCODINGTYPE, qLocationsClass, (ExpressionFactory) (AbstractQLocations qPath) -> qPath.encodingType); + addEntry(EntityProperty.LOCATION, qLocationsClass, "j", (ExpressionFactory) (AbstractQLocations qPath) -> qPath.location); + addEntry(EntityProperty.LOCATION, qLocationsClass, "g", (ExpressionFactory) (AbstractQLocations qPath) -> qPath.geom); + addEntry(EntityProperty.PROPERTIES, qLocationsClass, (ExpressionFactory) (AbstractQLocations qPath) -> qPath.properties); + + Class qObsPropertiesClass = qCollection.qObsProperties.getClass(); + addEntry(EntityProperty.ID, qObsPropertiesClass, (ExpressionFactory) AbstractQObsProperties::getId); + addEntry(EntityProperty.SELFLINK, qObsPropertiesClass, (ExpressionFactory) AbstractQObsProperties::getId); + addEntry(EntityProperty.DEFINITION, qObsPropertiesClass, (ExpressionFactory) (AbstractQObsProperties qPath) -> qPath.definition); + addEntry(EntityProperty.DESCRIPTION, qObsPropertiesClass, (ExpressionFactory) (AbstractQObsProperties qPath) -> qPath.description); + addEntry(EntityProperty.NAME, qObsPropertiesClass, (ExpressionFactory) (AbstractQObsProperties qPath) -> qPath.name); + addEntry(EntityProperty.PROPERTIES, qObsPropertiesClass, (ExpressionFactory) (AbstractQObsProperties qPath) -> qPath.properties); + + Class qObservationsClass = qCollection.qObservations.getClass(); + addEntry(EntityProperty.ID, qObservationsClass, (ExpressionFactory) AbstractQObservations::getId); + addEntry(EntityProperty.SELFLINK, qObservationsClass, (ExpressionFactory) AbstractQObservations::getId); + addEntry(EntityProperty.PARAMETERS, qObservationsClass, (ExpressionFactory) (AbstractQObservations qPath) -> qPath.parameters); + addEntry(EntityProperty.PHENOMENONTIME, qObservationsClass, KEY_TIME_INTERVAL_START, (ExpressionFactory) (AbstractQObservations qPath) -> qPath.phenomenonTimeStart); + addEntry(EntityProperty.PHENOMENONTIME, qObservationsClass, KEY_TIME_INTERVAL_END, (ExpressionFactory) (AbstractQObservations qPath) -> qPath.phenomenonTimeEnd); + addEntry(EntityProperty.RESULT, qObservationsClass, "n", (ExpressionFactory) (AbstractQObservations qPath) -> qPath.resultNumber); + addEntry(EntityProperty.RESULT, qObservationsClass, "b", (ExpressionFactory) (AbstractQObservations qPath) -> qPath.resultBoolean); + addEntry(EntityProperty.RESULT, qObservationsClass, "s", (ExpressionFactory) (AbstractQObservations qPath) -> qPath.resultString); + addEntry(EntityProperty.RESULT, qObservationsClass, "j", (ExpressionFactory) (AbstractQObservations qPath) -> qPath.resultJson); + addEntry(EntityProperty.RESULT, qObservationsClass, "t", (ExpressionFactory) (AbstractQObservations qPath) -> qPath.resultType); + addEntry(EntityProperty.RESULTQUALITY, qObservationsClass, (ExpressionFactory) (AbstractQObservations qPath) -> qPath.resultQuality); + addEntry(EntityProperty.RESULTTIME, qObservationsClass, (ExpressionFactory) (AbstractQObservations qPath) -> qPath.resultTime); + addEntry(EntityProperty.VALIDTIME, qObservationsClass, KEY_TIME_INTERVAL_START, (ExpressionFactory) (AbstractQObservations qPath) -> qPath.validTimeStart); + addEntry(EntityProperty.VALIDTIME, qObservationsClass, KEY_TIME_INTERVAL_END, (ExpressionFactory) (AbstractQObservations qPath) -> qPath.validTimeEnd); + addEntry(NavigationProperty.FEATUREOFINTEREST, qObservationsClass, (ExpressionFactory) AbstractQObservations::getFeatureId); + addEntry(NavigationProperty.DATASTREAM, qObservationsClass, (ExpressionFactory) AbstractQObservations::getDatastreamId); + addEntry(NavigationProperty.MULTIDATASTREAM, qObservationsClass, (ExpressionFactory) AbstractQObservations::getMultiDatastreamId); + + Class qSensorsClass = qCollection.qSensors.getClass(); + addEntry(EntityProperty.ID, qSensorsClass, (ExpressionFactory) AbstractQSensors::getId); + addEntry(EntityProperty.SELFLINK, qSensorsClass, (ExpressionFactory) AbstractQSensors::getId); + addEntry(EntityProperty.NAME, qSensorsClass, (ExpressionFactory) (AbstractQSensors qPath) -> qPath.name); + addEntry(EntityProperty.DESCRIPTION, qSensorsClass, (ExpressionFactory) (AbstractQSensors qPath) -> qPath.description); + addEntry(EntityProperty.ENCODINGTYPE, qSensorsClass, (ExpressionFactory) (AbstractQSensors qPath) -> qPath.encodingType); + addEntry(EntityProperty.METADATA, qSensorsClass, (ExpressionFactory) (AbstractQSensors qPath) -> qPath.metadata); + addEntry(EntityProperty.PROPERTIES, qSensorsClass, (ExpressionFactory) (AbstractQSensors qPath) -> qPath.properties); + + Class qThingsClass = qCollection.qThings.getClass(); + addEntry(EntityProperty.ID, qThingsClass, (ExpressionFactory) AbstractQThings::getId); + addEntry(EntityProperty.SELFLINK, qThingsClass, (ExpressionFactory) AbstractQThings::getId); + addEntry(EntityProperty.NAME, qThingsClass, (ExpressionFactory) (AbstractQThings qPath) -> qPath.name); + addEntry(EntityProperty.DESCRIPTION, qThingsClass, (ExpressionFactory) (AbstractQThings qPath) -> qPath.description); + addEntry(EntityProperty.PROPERTIES, qThingsClass, (ExpressionFactory) (AbstractQThings qPath) -> qPath.properties); + } + + public BasicPersistenceType getBasicPersistenceType() { + return basicPersistenceType; + } + + /** + * + * @param qPath The path to get expressions for. + * @param target The list to add to. If null a new list will be created. + * @return The target list, or a new list if target was null. + */ + public Collection expressionsForClass(Path qPath, Collection target) { + List list = allForClass.get(qPath.getClass()); + if (target == null) { + target = new ArrayList<>(); + } + for (ExpressionFactory f : list) { + target.add(f.get(qPath)); + } + return target; + } + + public Expression expressionForProperty(EntityProperty property, Path qPath) { + Map innerMap = epMapSingle.get(property); + if (innerMap == null) { + throw new IllegalArgumentException("ObservedProperty has no property called " + property.toString()); + } + return innerMap.get(qPath.getClass()).get(qPath); + } + + /** + * Get a list of expressions for the given property and path. Add it to the + * given list, or a new list. + * + * @param property The property to get expressions for. + * @param qPath The path to get expressions for. + * @param target The list to add to. If null a new list will be created. + * @return The target list, or a new list if target was null. + */ + public Collection expressionsForProperty(Property property, Path qPath, Collection target) { + Map> innerMap = epMapMulti.get(property); + if (innerMap == null) { + return target; + } + Map coreMap = innerMap.get(qPath.getClass()); + if (target == null) { + target = new ArrayList<>(); + } + for (Map.Entry es : coreMap.entrySet()) { + target.add(es.getValue().get(qPath)); + } + return target; + } + + /** + * Get a Map of expressions for the given property and path. Add it to the + * given Map, or a new Map. + * + * @param property The property to get expressions for. + * @param qPath The path to get expressions for. + * @param target The Map to add to. If null a new Map will be created. + * @return The target Map, or a new Map if target was null. + */ + public Map expressionsForProperty(EntityProperty property, Path qPath, Map target) { + Map> innerMap = epMapMulti.get(property); + if (innerMap == null) { + throw new IllegalArgumentException("We do not know any property called " + property.toString()); + } + Map coreMap = innerMap.get(qPath.getClass()); + if (coreMap == null) { + throw new IllegalArgumentException("No property called " + property.toString() + " for " + qPath.getClass()); + } + if (target == null) { + target = new LinkedHashMap<>(); + } + for (Map.Entry es : coreMap.entrySet()) { + target.put(es.getKey(), es.getValue().get(qPath)); + } + return target; + } + + /** + * Get the set of expressions for the given set of selected properties. + * + * @param qPath The entity path to get the expressions for. + * @param selectedProperties The set of properties to get the expressions + * of. + * @return The set of expressions. + */ + public Expression[] getExpressions(Path qPath, Set selectedProperties) { + Set exprSet = new HashSet<>(); + if (selectedProperties.isEmpty()) { + expressionsForClass(qPath, exprSet); + } else { + for (Property property : selectedProperties) { + expressionsForProperty(property, qPath, exprSet); + } + } + return exprSet.toArray(new Expression[exprSet.size()]); + } + + private void addEntry(Property property, Class clazz, ExpressionFactory factory) { + addEntrySingle(property, clazz, factory); + addEntryMulti(property, clazz, null, factory); + addToAll(clazz, factory); + } + + private void addEntry(Property property, Class clazz, String name, ExpressionFactory factory) { + addEntrySingle(property, clazz, factory); + addEntryMulti(property, clazz, name, factory); + addToAll(clazz, factory); + } + + private void addToAll(Class clazz, ExpressionFactory factory) { + List list = allForClass.computeIfAbsent( + clazz, + k -> new ArrayList<>() + ); + list.add(factory); + } + + private void addEntrySingle(Property property, Class clazz, ExpressionFactory factory) { + Map innerMap = epMapSingle.computeIfAbsent( + property, + k -> new HashMap<>() + ); + if (innerMap.containsKey(clazz)) { + LOGGER.trace("Class {} already has a registration for {}.", clazz.getName(), property); + return; + } + innerMap.put(clazz, factory); + } + + private void addEntryMulti(Property property, Class clazz, String name, ExpressionFactory factory) { + Map> innerMap = epMapMulti.computeIfAbsent( + property, + k -> new HashMap<>() + ); + Map coreMap = innerMap.computeIfAbsent( + clazz, + k -> new LinkedHashMap<>() + ); + if (name == null) { + name = Integer.toString(coreMap.size()); + } + coreMap.put(name, factory); + } +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/ResultType.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/ResultType.java index 701ed873a..690890bde 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/ResultType.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/ResultType.java @@ -29,7 +29,7 @@ public enum ResultType { private final Byte sqlValue; private ResultType() { - this.sqlValue = Integer.valueOf(ordinal()).byteValue(); + this.sqlValue = (byte) ordinal(); } public Byte sqlValue() { diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/Utils.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/Utils.java new file mode 100644 index 000000000..dd85228ad --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/Utils.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.EntityParser; +import de.fraunhofer.iosb.ilt.sta.json.deserialize.custom.GeoJsonDeserializier; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; +import java.io.IOException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author scf + */ +public class Utils { + + public static final String INTERVAL_0 = "({0})::interval"; + public static final String INTERVAL_1 = "({1})::interval"; + public static final String TIMESTAMP_0 = "({0})::timestamp"; + public static final String TIMESTAMP_1 = "({1})::timestamp"; + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); + private static final String FAILED_JSON_PARSE = "Failed to parse stored json."; + + private Utils() { + // Utility class, should not be instantiated. + } + + public static TimeInterval intervalFromTimes(Timestamp timeStart, Timestamp timeEnd) { + if (timeStart == null) { + timeStart = Timestamp.valueOf(LocalDateTime.MAX); + } + if (timeEnd == null) { + timeEnd = Timestamp.valueOf(LocalDateTime.MIN); + } + if (timeEnd.before(timeStart)) { + return null; + } else { + return TimeInterval.create(timeStart.getTime(), timeEnd.getTime()); + } + } + + public static TimeInstant instantFromTime(Timestamp time) { + if (time == null) { + return new TimeInstant(null); + } + return TimeInstant.create(time.getTime()); + } + + public static TimeValue valueFromTimes(Timestamp timeStart, Timestamp timeEnd) { + if (timeEnd == null || timeEnd.equals(timeStart)) { + return instantFromTime(timeStart); + } + return intervalFromTimes(timeStart, timeEnd); + } + + public static Object locationFromEncoding(String encodingType, String locationString) { + if (locationString == null || locationString.isEmpty()) { + return null; + } + if (encodingType != null && GeoJsonDeserializier.ENCODINGS.contains(encodingType.toLowerCase())) { + try { + return new GeoJsonDeserializier().deserialize(locationString); + } catch (IOException ex) { + LOGGER.error("Failed to deserialise geoJson."); + + } + } else { + try { + return jsonToObject(locationString, Map.class); + } catch (Exception e) { + LOGGER.trace("Not a map."); + } + return locationString; + } + return null; + } + + public static JsonNode jsonToTree(String json) { + if (json == null) { + return null; + } + + try { + return EntityParser.getSimpleObjectMapper().readTree(json); + } catch (IOException ex) { + throw new IllegalStateException(FAILED_JSON_PARSE, ex); + } + } + + public static T jsonToObject(String json, Class clazz) { + if (json == null) { + return null; + } + try { + return EntityParser.getSimpleObjectMapper().readValue(json, clazz); + } catch (IOException ex) { + throw new IllegalStateException(FAILED_JSON_PARSE, ex); + } + } + + public static T jsonToObject(String json, TypeReference typeReference) { + if (json == null) { + return null; + } + try { + return EntityParser.getSimpleObjectMapper().readValue(json, typeReference); + } catch (IOException ex) { + throw new IllegalStateException(FAILED_JSON_PARSE, ex); + } + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ConstantDateTimeExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ConstantDateTimeExpression.java deleted file mode 100644 index 69ce2bdd0..000000000 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ConstantDateTimeExpression.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.expression; - -import com.querydsl.core.types.Constant; -import com.querydsl.core.types.ConstantImpl; -import com.querydsl.core.types.Visitor; -import com.querydsl.core.types.dsl.DateTimeExpression; -import java.sql.Timestamp; -import javax.annotation.Nullable; - -/** - * @author scf - */ -public class ConstantDateTimeExpression extends DateTimeExpression { - - /** - * Flag indicating that the original time given was in utc. - */ - private boolean utc = true; - - /** - * - * @param ts The constant timestamp. - * @param utc Flag indicating that the original time given was in utc. - */ - public ConstantDateTimeExpression(final Timestamp ts, boolean utc) { - super(ConstantImpl.create(ts)); - this.utc = utc; - } - - /** - * @return Flag indicating that the original time given was in utc. - */ - public boolean isUtc() { - return utc; - } - - @Override - @Nullable - public R accept(Visitor v, C context) { - return v.visit((Constant) mixin, context); - } - -} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ConstantDurationExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ConstantDurationExpression.java deleted file mode 100644 index ffefd5cb0..000000000 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ConstantDurationExpression.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.expression; - -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Visitor; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.DateTimeExpression; -import com.querydsl.core.types.dsl.DateTimeTemplate; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.StringExpression; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; -import de.fraunhofer.iosb.ilt.sta.query.expression.constant.DurationConstant; -import java.sql.Timestamp; - -/** - * - * @author scf - */ -public class ConstantDurationExpression implements TimeExpression { - - final StringExpression duration; - - public ConstantDurationExpression(final DurationConstant duration) { - this.duration = new ConstantStringExpression(duration.asISO8601()); - } - - public ConstantDurationExpression(final StringExpression duration) { - this.duration = duration; - } - - public StringExpression getDuration() { - return duration; - } - - @Override - public Object accept(Visitor vstr, Object c) { - throw new UnsupportedOperationException("visit on ConstantDurationExpression not supported."); - } - - @Override - public Class getType() { - return DurationConstant.class; - } - - @Override - public BooleanExpression eq(Expression other) { - if (other instanceof ConstantDurationExpression) { - return simpleOpBool("=", other); - } - throw new UnsupportedOperationException("Can not compare Duration to " + other.getClass().getName()); - } - - @Override - public BooleanExpression neq(Expression other) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public BooleanExpression gt(Expression other) { - return eq(other).not(); - } - - @Override - public BooleanExpression goe(Expression other) { - if (other instanceof ConstantDurationExpression) { - return simpleOpBool(">=", other); - } - throw new UnsupportedOperationException("Can not compare Duration to " + other.getClass().getName()); - } - - @Override - public BooleanExpression lt(Expression other) { - if (other instanceof ConstantDurationExpression) { - return simpleOpBool("<", other); - } - throw new UnsupportedOperationException("Can not compare Duration to " + other.getClass().getName()); - } - - @Override - public BooleanExpression loe(Expression other) { - if (other instanceof ConstantDurationExpression) { - return simpleOpBool("<=", other); - } - throw new UnsupportedOperationException("Can not compare Duration to " + other.getClass().getName()); - } - - @Override - public Expression add(Expression other) { - return simpleOp("+", other); - } - - @Override - public Expression sub(Expression other) { - return simpleOp("-", other); - } - - @Override - public Expression subi(Expression other) { - return simpleOp("-", other, true); - } - - @Override - public Expression mul(Expression other) { - return simpleOp("*", other); - } - - @Override - public Expression div(Expression other) { - return simpleOp("/", other); - } - - private Expression simpleOp(String op, Expression other) { - return simpleOp(op, other, false); - } - - private Expression simpleOp(String op, Expression other, boolean inverse) { - if (other instanceof ConstantDurationExpression) { - ConstantDurationExpression cd = (ConstantDurationExpression) other; - String template = inverse ? "({1}::interval " + op + " {0}::interval)" : "({0}::interval " + op + " {1}::interval)"; - return Expressions.dateTimeTemplate(Timestamp.class, template, this.duration, cd.duration); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti = (TimeIntervalExpression) other; - DateTimeExpression dtEnd = PgExpressionHandler.checkType(DateTimeExpression.class, ti.end, false); - DateTimeExpression dtStart = PgExpressionHandler.checkType(DateTimeExpression.class, ti.start, false); - String template = inverse ? "({1}::timestamp " + op + " {0}::interval)" : "({0}::interval " + op + " {1}::timestamp)"; - DateTimeTemplate newStart = Expressions.dateTimeTemplate(Timestamp.class, template, duration, dtStart); - DateTimeTemplate newEnd = Expressions.dateTimeTemplate(Timestamp.class, template, duration, dtEnd); - return new TimeIntervalExpression(newStart, newEnd); - } - if (other instanceof DateTimeExpression) { - DateTimeExpression dt = (DateTimeExpression) other; - String template = inverse ? "({1}::timestamp " + op + " {0}::interval)" : "({0}::interval " + op + " {1}::timestamp)"; - return Expressions.dateTimeTemplate(Timestamp.class, template, duration, dt); - } - throw new UnsupportedOperationException("Can not add, sub, mul or div with Duration and " + other.getClass().getName()); - } - - private BooleanExpression simpleOpBool(String op, Expression other) { - if (other instanceof ConstantDurationExpression) { - ConstantDurationExpression cd = (ConstantDurationExpression) other; - String template = "({0}::interval " + op + " {1}::interval)"; - return Expressions.booleanTemplate(template, this.duration, cd.duration); - } - throw new UnsupportedOperationException("Can not compare between Duration and " + other.getClass().getName()); - } - -} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/JsonExpressionFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/JsonExpressionFactory.java index de3e5db90..4d1196d03 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/JsonExpressionFactory.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/JsonExpressionFactory.java @@ -34,11 +34,12 @@ */ public class JsonExpressionFactory { - private static enum CompareType { + private enum CompareType { NUMBER, BOOLEAN, STRING } + public static final String KEY_JSONB = "j"; public static final String KEY_NUMBER = "n"; public static final String KEY_STRING = "s"; @@ -50,7 +51,7 @@ public static class ListExpressionJson extends ListExpression { private final ComparableTemplate jsonExpression; - public ListExpressionJson(Map> expressions, Map> expressionsForOrder, ComparableTemplate jsonExpression) { + public ListExpressionJson(Map expressions, Map expressionsForOrder, ComparableTemplate jsonExpression) { super(expressions, expressionsForOrder); this.jsonExpression = jsonExpression; } @@ -161,7 +162,7 @@ private Predicate createTypePredicate(CompareType other) { return null; } } - }; + } public JsonExpressionFactory(Expression jsonField) { this.jsonField = jsonField; @@ -172,7 +173,7 @@ public JsonExpressionFactory addToPath(String key) { return this; } - public Expression build() { + public ListExpressionJson build() { StringBuilder templateCore = new StringBuilder(); boolean firstDone = false; for (String key : path) { @@ -189,8 +190,8 @@ public Expression build() { String templateNumber = "safe_cast_to_numeric({0}::jsonb#>'{ " + templateCoreString + " }')"; String templateBoolean = "safe_cast_to_boolean({0}::jsonb#>'{ " + templateCoreString + " }')"; - Map> expressions = new HashMap<>(); - Map> expressionsForOrder = new HashMap<>(); + Map expressions = new HashMap<>(); + Map expressionsForOrder = new HashMap<>(); StringTemplate stringTemplate = Expressions.stringTemplate(templateString, jsonField); // TODO: Review if this should change to the jsonb field. expressionsForOrder.put("s", stringTemplate); @@ -200,9 +201,7 @@ public Expression build() { ComparableTemplate jsonExpression = Expressions.comparableTemplate(String.class, templateJsonb, jsonField); expressions.put(KEY_JSONB, jsonExpression); - ListExpression listExpression = new ListExpressionJson(expressions, expressionsForOrder, jsonExpression); - - return listExpression; + return new ListExpressionJson(expressions, expressionsForOrder, jsonExpression); } } diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ListExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ListExpression.java index 607dc3e2f..e39e9bf94 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ListExpression.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/ListExpression.java @@ -28,28 +28,28 @@ */ public class ListExpression implements Expression { - private final Map> expressions; - private final Map> expressionsForOrder; + private final Map expressions; + private final Map expressionsForOrder; - public ListExpression(Map> expressions) { + public ListExpression(Map expressions) { this.expressions = expressions; this.expressionsForOrder = expressions; } - public ListExpression(Map> expressions, Map> expressionsForOrder) { + public ListExpression(Map expressions, Map expressionsForOrder) { this.expressions = expressions; this.expressionsForOrder = expressionsForOrder; } - public Map> getExpressions() { + public Map getExpressions() { return expressions; } - public Map> getExpressionsForOrder() { + public Map getExpressionsForOrder() { return expressionsForOrder; } - public Expression getExpression(String name) { + public Expression getExpression(String name) { return expressions.get(name); } diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaDateTimeExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaDateTimeExpression.java new file mode 100644 index 000000000..6e895fb75 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaDateTimeExpression.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2016 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.sta.persistence.postgres.expression; + +import com.querydsl.core.types.ConstantImpl; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Visitor; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.DateTimeExpression; +import com.querydsl.core.types.dsl.DateTimeTemplate; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberExpression; +import com.querydsl.core.types.dsl.StringTemplate; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.INTERVAL_1; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.TIMESTAMP_0; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.TIMESTAMP_1; +import de.fraunhofer.iosb.ilt.sta.query.expression.constant.DurationConstant; +import java.sql.Timestamp; + +/** + * @author scf + */ +public class StaDateTimeExpression implements TimeExpression { + + public static DateTimeExpression createDateTimeExpression(final Expression source) { + return new DateTimeExpression(source) { + + @Override + public R accept(Visitor v, C context) { + return this.mixin.accept(v, context); + } + }; + } + /** + * Flag indicating that the original time given was in utc. + */ + private boolean utc = true; + private final DateTimeExpression mixin; + + /** + * + * @param ts The constant timestamp. + * @param utc Flag indicating that the original time given was in utc. + */ + public StaDateTimeExpression(final Timestamp ts, boolean utc) { + mixin = createDateTimeExpression(ConstantImpl.create(ts)); + this.utc = utc; + } + + public StaDateTimeExpression(DateTimeExpression mixin) { + this.mixin = mixin; + } + + /** + * @return Flag indicating that the original time given was in utc. + */ + @Override + public boolean isUtc() { + return utc; + } + + @Override + public DateTimeExpression getDateTime() { + return mixin; + } + + @Override + public Class getType() { + return DurationConstant.class; + } + + @Override + public Object accept(Visitor vstr, Object c) { + return mixin.accept(vstr, c); + } + + private Expression specificOp(String op, StaDurationExpression other) { + switch (op) { + case "+": + case "-": + String template = "(" + TIMESTAMP_0 + " " + op + " " + INTERVAL_1 + ")"; + DateTimeTemplate expression = Expressions.dateTimeTemplate(Timestamp.class, template, mixin, other.getDuration()); + return new StaDateTimeExpression(expression); + + default: + throw new UnsupportedOperationException("Can not mul or div a DateTime with a " + other.getClass().getName()); + } + } + + private Expression specificOp(String op, StaDateTimeExpression other) { + if ("-".equals(op)) { + String template = "(" + TIMESTAMP_0 + " " + op + " " + TIMESTAMP_1 + ")"; + StringTemplate expression = Expressions.stringTemplate(template, mixin, other.getDateTime()); + return new StaDurationExpression(expression); + } else { + throw new UnsupportedOperationException("Can not add, mul or div two DateTimes."); + } + } + + @Override + public Expression simpleOp(String op, Expression other) { + if (other instanceof StaDurationExpression) { + return specificOp(op, (StaDurationExpression) other); + } + if (other instanceof StaDateTimeExpression) { + return specificOp(op, (StaDateTimeExpression) other); + } + throw new UnsupportedOperationException("Can not add, sub, mul or div a DateTime with a " + other.getClass().getName()); + } + + private BooleanExpression specificOpBool(String op, StaDateTimeExpression other) { + DateTimeExpression t1 = mixin; + DateTimeExpression t2 = other.mixin; + switch (op) { + case "=": + return t1.eq(t2); + + case ">": + return t1.gt(t2); + + case ">=": + return t1.goe(t2); + + case "<": + return t1.lt(t2); + + case "<=": + return t1.loe(t2); + + case "a": + return t1.gt(t2); + + case "b": + return t1.lt(t2); + + case "c": + throw new UnsupportedOperationException("First parameter of contains must be an interval."); + + case "m": + return t1.eq(t2); + + case "o": + return t1.eq(t2); + + case "s": + return t1.eq(t2); + + case "f": + return t1.eq(t2); + + default: + throw new UnsupportedOperationException("Unknown boolean operation: " + op); + } + } + + private BooleanExpression specificOpBool(String op, StaTimeIntervalExpression other) { + DateTimeExpression t1 = mixin; + DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, other.getStart(), false); + DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, other.getEnd(), false); + switch (op) { + case "=": + return t1.eq(s2).and(t1.eq(e2)); + + case ">": + return t1.goe(e2).and(t1.gt(s2)); + + case ">=": + return t1.goe(e2); + + case "<": + return t1.lt(s2); + + case "<=": + return t1.loe(s2); + + case "a": + return t1.goe(e2).and(t1.gt(s2)); + + case "b": + return t1.lt(s2); + + case "c": + throw new UnsupportedOperationException("First parameter of contains must be an interval."); + + case "m": + return t1.eq(s2).or(t1.eq(e2)); + + case "o": + return t1.eq(s2).or(s2.loe(t1).and(e2.gt(t1))); + + case "s": + return t1.eq(s2); + + case "f": + return t1.eq(e2); + + default: + throw new UnsupportedOperationException("Unknown boolean operation: " + op); + } + } + + @Override + public BooleanExpression simpleOpBool(String op, Expression other) { + if (other instanceof StaDateTimeExpression) { + return specificOpBool(op, (StaDateTimeExpression) other); + } + if (other instanceof StaTimeIntervalExpression) { + return specificOpBool(op, (StaTimeIntervalExpression) other); + } + throw new UnsupportedOperationException("Can not compare between Duration and " + other.getClass().getName()); + } + + @Override + public NumberExpression year() { + return getDateTime().year(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaDurationExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaDurationExpression.java new file mode 100644 index 000000000..94f71fe28 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaDurationExpression.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 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.sta.persistence.postgres.expression; + +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Visitor; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.DateTimeExpression; +import com.querydsl.core.types.dsl.DateTimeTemplate; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberExpression; +import com.querydsl.core.types.dsl.NumberOperation; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.core.types.dsl.StringTemplate; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.INTERVAL_0; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.INTERVAL_1; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.TIMESTAMP_1; +import de.fraunhofer.iosb.ilt.sta.query.expression.constant.DurationConstant; +import java.sql.Timestamp; + +/** + * + * @author scf + */ +public class StaDurationExpression implements TimeExpression { + + private final StringExpression duration; + + public StaDurationExpression(final DurationConstant duration) { + this.duration = new ConstantStringExpression(duration.asISO8601()); + } + + public StaDurationExpression(final StringExpression duration) { + this.duration = duration; + } + + @Override + public DateTimeExpression getDateTime() { + throw new UnsupportedOperationException("Can not convert duration to DateTime."); + } + + @Override + public boolean isUtc() { + // durations are always utc. + return true; + } + + public StringExpression getDuration() { + return duration; + } + + @Override + public Class getType() { + return DurationConstant.class; + } + + @Override + public Object accept(Visitor vstr, Object c) { + return duration.accept(vstr, c); + } + + @Override + public BooleanExpression after(Expression other) { + throw new UnsupportedOperationException("Can not use after with duration."); + } + + @Override + public BooleanExpression before(Expression other) { + throw new UnsupportedOperationException("Can not use before with duration."); + } + + @Override + public BooleanExpression meets(Expression other) { + throw new UnsupportedOperationException("Can not use meets with duration."); + } + + @Override + public BooleanExpression contains(Expression other) { + throw new UnsupportedOperationException("Can not use contais with duration."); + } + + @Override + public BooleanExpression overlaps(Expression other) { + throw new UnsupportedOperationException("Can not use overlaps with duration."); + } + + @Override + public BooleanExpression starts(Expression other) { + throw new UnsupportedOperationException("Can not use starts with duration."); + } + + @Override + public BooleanExpression finishes(Expression other) { + throw new UnsupportedOperationException("Can not use finishes with duration."); + } + + private Expression specificOp(String op, StaDurationExpression other) { + String template = "(" + INTERVAL_0 + " " + op + " " + INTERVAL_1 + ")"; + DateTimeTemplate expression = Expressions.dateTimeTemplate(Timestamp.class, template, this.duration, other.duration); + return new StaDateTimeExpression(expression); + } + + private Expression specificOp(String op, StaTimeIntervalExpression other) { + DateTimeExpression dtEnd = PgExpressionHandler.checkType(DateTimeExpression.class, other.end, false); + DateTimeExpression dtStart = PgExpressionHandler.checkType(DateTimeExpression.class, other.start, false); + String template = "(" + INTERVAL_0 + " " + op + " " + TIMESTAMP_1 + ")"; + DateTimeTemplate newStart = Expressions.dateTimeTemplate(Timestamp.class, template, duration, dtStart); + DateTimeTemplate newEnd = Expressions.dateTimeTemplate(Timestamp.class, template, duration, dtEnd); + return new StaTimeIntervalExpression(newStart, newEnd); + } + + private Expression specificOp(String op, StaDateTimeExpression other) { + String template = "(" + INTERVAL_0 + " " + op + " " + TIMESTAMP_1 + ")"; + DateTimeTemplate expression = Expressions.dateTimeTemplate(Timestamp.class, template, duration, other); + return new StaDateTimeExpression(expression); + } + + private Expression specificOp(String op, NumberExpression other) { + switch (op) { + case "*": + case "/": + String template = "(" + INTERVAL_0 + " " + op + " ({1}))"; + StringTemplate expression = Expressions.stringTemplate(template, this.duration, other); + return new StaDurationExpression(expression); + + default: + throw new UnsupportedOperationException("Can not '" + op + "' with Duration and " + other.getClass().getName()); + } + } + + @Override + public Expression simpleOp(String op, Expression other) { + if (other instanceof StaDurationExpression) { + return specificOp(op, (StaDurationExpression) other); + } + if (other instanceof StaTimeIntervalExpression) { + return specificOp(op, (StaTimeIntervalExpression) other); + } + if (other instanceof StaDateTimeExpression) { + return specificOp(op, (StaDateTimeExpression) other); + } + if (other instanceof NumberOperation) { + return specificOp(op, (NumberOperation) other); + } + if (other instanceof ListExpression) { + NumberExpression nrOther = PgExpressionHandler.getSingleOfType(NumberExpression.class, other); + return specificOp(op, nrOther); + } + throw new UnsupportedOperationException("Can not add, sub, mul or div with Duration and " + other.getClass().getName()); + } + + @Override + public BooleanExpression simpleOpBool(String op, Expression other) { + if (other instanceof StaDurationExpression) { + StaDurationExpression cd = (StaDurationExpression) other; + String template = "(" + INTERVAL_0 + " " + op + " " + INTERVAL_1 + ")"; + return Expressions.booleanTemplate(template, this.duration, cd.duration); + } + throw new UnsupportedOperationException("Can not compare between Duration and " + other.getClass().getName()); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaTimeIntervalExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaTimeIntervalExpression.java new file mode 100644 index 000000000..5b9a63192 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/StaTimeIntervalExpression.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 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.sta.persistence.postgres.expression; + +import com.querydsl.core.types.ConstantImpl; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Visitor; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.DateTimeExpression; +import com.querydsl.core.types.dsl.DateTimeTemplate; +import com.querydsl.core.types.dsl.Expressions; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.INTERVAL_1; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.TIMESTAMP_0; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils.TIMESTAMP_1; +import java.sql.Timestamp; +import java.util.Map; + +/** + * Some paths point to time-intervals that return two column references. If the + * references include a start and end time, they are treated as a time interval. + */ +public class StaTimeIntervalExpression implements TimeExpression { + + public static final String KEY_TIME_INTERVAL_START = "tStart"; + public static final String KEY_TIME_INTERVAL_END = "tEnd"; + private static final String INCOMPATIBLE_OP = "Incompatible operator: Interval '"; + /** + * Flag indicating that the original time given was in utc. + */ + private boolean utc = true; + final DateTimeExpression start; + final DateTimeExpression end; + + public StaTimeIntervalExpression(Map expressions) { + this.start = (DateTimeExpression) expressions.get(KEY_TIME_INTERVAL_START); + this.end = (DateTimeExpression) expressions.get(KEY_TIME_INTERVAL_END); + } + + public StaTimeIntervalExpression(DateTimeExpression start, DateTimeExpression end) { + this.start = start; + this.end = end; + } + + public StaTimeIntervalExpression(Timestamp start, Timestamp end) { + this.start = StaDateTimeExpression.createDateTimeExpression(ConstantImpl.create(start)); + this.end = StaDateTimeExpression.createDateTimeExpression(ConstantImpl.create(end)); + } + + public DateTimeExpression getStart() { + return start; + } + + public DateTimeExpression getEnd() { + return end; + } + + @Override + public DateTimeExpression getDateTime() { + return start; + } + + @Override + public boolean isUtc() { + return utc; + } + + @Override + public Object accept(Visitor vstr, Object c) { + throw new UnsupportedOperationException("visit on TimeIntervalExpression not supported."); + } + + @Override + public Class getType() { + return TimeInterval.class; + } + + private Expression specificOp(String op, StaDurationExpression other) { + switch (op) { + case "+": + case "-": + DateTimeExpression dtEnd = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); + DateTimeExpression dtStart = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); + String template = "(" + TIMESTAMP_0 + " " + op + " " + INTERVAL_1 + ")"; + DateTimeTemplate newStart = Expressions.dateTimeTemplate(Timestamp.class, template, dtStart, other.getDuration()); + DateTimeTemplate newEnd = Expressions.dateTimeTemplate(Timestamp.class, template, dtEnd, other.getDuration()); + return new StaTimeIntervalExpression(newStart, newEnd); + + default: + throw new UnsupportedOperationException(INCOMPATIBLE_OP + op + "' " + other.getClass().getName()); + + } + } + + private Expression specificOp(String op, StaDateTimeExpression other) { + if ("-".equals(op)) { + // We calculate with the start time and return a duration. + DateTimeExpression dtStart = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); + String template = "(" + TIMESTAMP_0 + " - " + TIMESTAMP_1 + ")"; + return new StaDurationExpression(Expressions.stringTemplate(template, dtStart, other.getDateTime())); + + } else { + throw new UnsupportedOperationException(INCOMPATIBLE_OP + op + "' " + other.getClass().getName()); + } + } + + private Expression specificOp(String op, StaTimeIntervalExpression other) { + if ("-".equals(op)) { + // We calculate with the start time and return a duration. + DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); + DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, other.start, false); + String template = "(" + TIMESTAMP_0 + " - " + TIMESTAMP_1 + ")"; + return new StaDurationExpression(Expressions.stringTemplate(template, s1, s2)); + + } else { + throw new UnsupportedOperationException(INCOMPATIBLE_OP + op + "' " + other.getClass().getName()); + } + } + + @Override + public Expression simpleOp(String op, Expression other) { + if (other instanceof StaDurationExpression) { + return specificOp(op, (StaDurationExpression) other); + } + if (other instanceof StaDateTimeExpression) { + return specificOp(op, (StaDateTimeExpression) other); + } + if (other instanceof StaTimeIntervalExpression) { + return specificOp(op, (StaTimeIntervalExpression) other); + } + throw new UnsupportedOperationException("Can not add, sub, mul or div with Duration and " + other.getClass().getName()); + } + + private BooleanExpression specificOpBool(String op, StaDateTimeExpression other) { + DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); + DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); + DateTimeExpression t2 = other.getDateTime(); + switch (op) { + case "=": + return s1.eq(t2).and(e1.eq(t2)); + + case ">": + return s1.gt(t2); + + case ">=": + return s1.goe(t2); + + case "<": + return e1.loe(t2).and(s1.lt(t2)); + + case "<=": + return e1.loe(t2); + + case "a": + return s1.gt(t2); + + case "b": + return e1.loe(t2).and(s1.lt(t2)); + + case "c": + return s1.loe(t2).and(e1.gt(t2)); + + case "m": + return s1.eq(t2).or(e1.eq(t2)); + + case "o": + return s1.eq(t2).or(s1.loe(t2).and(e1.gt(t2))); + + case "s": + return s1.eq(t2); + + case "f": + return e1.eq(t2); + + default: + throw new UnsupportedOperationException("Unknown boolean operation: " + op); + } + } + + private BooleanExpression specificOpBool(String op, StaTimeIntervalExpression other) { + DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); + DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); + DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, other.getStart(), false); + DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, other.getEnd(), false); + switch (op) { + case "=": + return s1.eq(s2).and(e1.eq(e2)); + + case ">": + return s1.goe(e2).and(s1.gt(s2)); + + case ">=": + return s1.goe(s2).and(e1.goe(e2)); + + case "<": + return e1.loe(s2).and(s1.lt(s2)); + + case "<=": + return s1.loe(s2).and(e1.loe(e2)); + + case "a": + return s1.goe(e2).and(s1.gt(s2)); + + case "b": + return e1.loe(s2).and(s1.lt(s2)); + + case "c": + return s1.loe(s2).and(e1.gt(s2)).and(e1.goe(e2)); + + case "m": + return s1.eq(e2).or(e1.eq(s2)); + + case "o": + return s1.goe(e2).or(s2.goe(e1)).not().or(s1.eq(s2)); + + case "s": + return s1.eq(s2); + + case "f": + return e1.eq(e2); + + default: + throw new UnsupportedOperationException("Unknown boolean operation: " + op); + } + } + + @Override + public BooleanExpression simpleOpBool(String op, Expression other) { + if (other instanceof StaDateTimeExpression) { + return specificOpBool(op, (StaDateTimeExpression) other); + } + if (other instanceof StaTimeIntervalExpression) { + return specificOpBool(op, (StaTimeIntervalExpression) other); + } + throw new UnsupportedOperationException("Can not compare between Duration and " + other.getClass().getName()); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/TimeExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/TimeExpression.java index b0b6f707e..3f9d3bb31 100644 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/TimeExpression.java +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/TimeExpression.java @@ -19,6 +19,9 @@ import com.querydsl.core.types.Expression; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.DateTimeExpression; +import com.querydsl.core.types.dsl.NumberExpression; +import java.sql.Timestamp; /** * @@ -26,32 +29,83 @@ */ public interface TimeExpression extends Expression { - public BooleanExpression eq(Expression other); + public DateTimeExpression getDateTime(); - public BooleanExpression neq(Expression other); + public boolean isUtc(); - public BooleanExpression gt(Expression other); + public default BooleanExpression eq(Expression other) { + return simpleOpBool("=", other); + } - public BooleanExpression goe(Expression other); + public default BooleanExpression neq(Expression other) { + return eq(other).not(); + } - public BooleanExpression lt(Expression other); + public default BooleanExpression gt(Expression other) { + return simpleOpBool(">", other); + } - public BooleanExpression loe(Expression other); + public default BooleanExpression goe(Expression other) { + return simpleOpBool(">=", other); + } - public Expression add(Expression other); + public default BooleanExpression lt(Expression other) { + return simpleOpBool("<", other); + } - public Expression sub(Expression other); + public default BooleanExpression loe(Expression other) { + return simpleOpBool("<=", other); + } - /** - * Inverse subtract (other - this) - * - * @param other the thing to subtract this from. - * @return other - this. - */ - public Expression subi(Expression other); + public default BooleanExpression after(Expression other) { + return simpleOpBool("a", other); + } - public Expression mul(Expression other); + public default BooleanExpression before(Expression other) { + return simpleOpBool("b", other); + } - public Expression div(Expression other); + public default BooleanExpression meets(Expression other) { + return simpleOpBool("m", other); + } + public default BooleanExpression contains(Expression other) { + return simpleOpBool("c", other); + } + + public default BooleanExpression overlaps(Expression other) { + return simpleOpBool("o", other); + } + + public default BooleanExpression starts(Expression other) { + return simpleOpBool("s", other); + } + + public default BooleanExpression finishes(Expression other) { + return simpleOpBool("f", other); + } + + public default Expression add(Expression other) { + return simpleOp("+", other); + } + + public default Expression sub(Expression other) { + return simpleOp("-", other); + } + + public default Expression mul(Expression other) { + return simpleOp("*", other); + } + + public default Expression div(Expression other) { + return simpleOp("/", other); + } + + public Expression simpleOp(String op, Expression other); + + public BooleanExpression simpleOpBool(String op, Expression other); + + public default NumberExpression year() { + return getDateTime().year(); + } } diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/TimeIntervalExpression.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/TimeIntervalExpression.java deleted file mode 100644 index 5b1d1d810..000000000 --- a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/expression/TimeIntervalExpression.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2016 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.sta.persistence.postgres.expression; - -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Visitor; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.DateTimeExpression; -import com.querydsl.core.types.dsl.DateTimeTemplate; -import com.querydsl.core.types.dsl.Expressions; -import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInterval; -import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PgExpressionHandler; -import java.sql.Timestamp; -import java.util.Map; - -/** - * Some paths point to time-intervals that return two column references. If the - * references include a start and end time, they are treated as a time interval. - */ -public class TimeIntervalExpression implements TimeExpression { - - public static final String KEY_TIME_INTERVAL_START = "tStart"; - public static final String KEY_TIME_INTERVAL_END = "tEnd"; - - final Expression start; - final Expression end; - - public TimeIntervalExpression(Map> expressions) { - this.start = expressions.get(KEY_TIME_INTERVAL_START); - this.end = expressions.get(KEY_TIME_INTERVAL_END); - } - - public TimeIntervalExpression(Expression start, Expression end) { - this.start = start; - this.end = end; - } - - public Expression getStart() { - return start; - } - - public Expression getEnd() { - return end; - } - - @Override - public Object accept(Visitor vstr, Object c) { - throw new UnsupportedOperationException("visit on TimeIntervalExpression not supported."); - } - - @Override - public Class getType() { - return TimeInterval.class; - } - - @Override - public BooleanExpression eq(Expression other) { - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.start, false); - DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.end, false); - return s1.eq(s2).and(e1.eq(e2)); - } - if (other instanceof DateTimeExpression) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - return s1.eq(other).and(e1.eq(other)); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName() + " for equality."); - } - - @Override - public BooleanExpression neq(Expression other) { - return eq(other).not(); - } - - @Override - public BooleanExpression gt(Expression other) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - if (other instanceof DateTimeExpression) { - return s1.gt(other); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti = (TimeIntervalExpression) other; - return s1.goe(ti.getEnd()).and(s1.gt(ti.getStart())); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - public BooleanExpression after(Expression other) { - return gt(other); - } - - @Override - public BooleanExpression goe(Expression other) { - if (other instanceof DateTimeExpression) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - return s1.goe(other); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.end, false); - return s1.goe(s2).and(e1.goe(e2)); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - @Override - public BooleanExpression lt(Expression other) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - if (other instanceof DateTimeExpression) { - return e1.loe(other).and(s1.lt(other)); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti = (TimeIntervalExpression) other; - return e1.loe(ti.getStart()).and(s1.lt(ti.getStart())); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - public BooleanExpression before(Expression other) { - return lt(other); - } - - @Override - public BooleanExpression loe(Expression other) { - if (other instanceof DateTimeExpression) { - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - return e1.loe(other); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.end, false); - return s1.loe(s2).and(e1.loe(e2)); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - public BooleanExpression meets(Expression other) { - if (other instanceof DateTimeExpression) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - return s1.eq(other).or(e1.eq(other)); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.end, false); - return s1.eq(e2).or(e1.eq(s2)); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - public BooleanExpression contains(Expression other) { - if (other instanceof DateTimeExpression) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - return s1.loe(other).and(e1.gt(other)); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.end, false); - return s1.loe(s2).and(e1.gt(s2)).and(e1.goe(e2)); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - public BooleanExpression overlaps(Expression other) { - if (other instanceof DateTimeExpression) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - return s1.eq(other).or(s1.loe(other).and(e1.gt(other))); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.start, false); - DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.end, false); - return s1.goe(e2).or(s2.goe(e1)).not().or(s1.eq(s2)); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - public BooleanExpression starts(Expression other) { - if (other instanceof DateTimeExpression) { - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - return s1.eq(other); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression s1 = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - DateTimeExpression s2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.start, false); - return s1.eq(s2); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - public BooleanExpression finishes(Expression other) { - if (other instanceof DateTimeExpression) { - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - return e1.eq(other); - } - if (other instanceof TimeIntervalExpression) { - TimeIntervalExpression ti2 = (TimeIntervalExpression) other; - DateTimeExpression e1 = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression e2 = PgExpressionHandler.checkType(DateTimeExpression.class, ti2.end, false); - return e1.eq(e2); - } - throw new UnsupportedOperationException("Can not compare TimeInterval to " + other.getClass().getName()); - } - - @Override - public TimeIntervalExpression add(Expression other) { - return simpleOp("+", other); - } - - @Override - public TimeExpression subi(Expression other) { - if (other instanceof DateTimeExpression) { - // We calculate with the start time and return a duration. - DateTimeExpression dtStart = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - String template = "({0}::timestamp - {1}::timestamp)"; - return new ConstantDurationExpression(Expressions.stringTemplate(template, other, dtStart)); - } - throw new UnsupportedOperationException("Can not subtract TimeInterval from anything."); - } - - @Override - public TimeExpression sub(Expression other) { - if (other instanceof DateTimeExpression) { - // We calculate with the start time and return a duration. - DateTimeExpression dtStart = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - String template = "({0}::timestamp - {1}::timestamp)"; - return new ConstantDurationExpression(Expressions.stringTemplate(template, dtStart, other)); - } - return simpleOp("-", other); - } - - @Override - public TimeIntervalExpression mul(Expression other) { - return simpleOp("*", other); - } - - @Override - public TimeIntervalExpression div(Expression other) { - return simpleOp("/", other); - } - - private TimeIntervalExpression simpleOp(String op, Expression other) { - if (other instanceof ConstantDurationExpression) { - ConstantDurationExpression cd = (ConstantDurationExpression) other; - DateTimeExpression dtEnd = PgExpressionHandler.checkType(DateTimeExpression.class, end, false); - DateTimeExpression dtStart = PgExpressionHandler.checkType(DateTimeExpression.class, start, false); - String template = "({0}::timestamp " + op + " {1}::interval)"; - DateTimeTemplate newStart = Expressions.dateTimeTemplate(Timestamp.class, template, dtStart, cd.duration); - DateTimeTemplate newEnd = Expressions.dateTimeTemplate(Timestamp.class, template, dtEnd, cd.duration); - return new TimeIntervalExpression(newStart, newEnd); - } - throw new UnsupportedOperationException("Can not add TimeInterval and " + other.getClass().getName()); - } - -} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/DatastreamFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/DatastreamFactory.java new file mode 100644 index 000000000..6ac8136a3 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/DatastreamFactory.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; +import de.fraunhofer.iosb.ilt.sta.model.Sensor; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.NO_ID_OR_NOT_FOUND; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.GeoHelper; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.geojson.Polygon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class DatastreamFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(DatastreamFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQDatastreams qInstance; + private final QCollection qCollection; + + public DatastreamFactory(EntityFactories factories, AbstractQDatastreams qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public Datastream create(Tuple tuple, Query query, DataSize dataSize) { + Set select = query == null ? Collections.emptySet() : query.getSelect(); + Datastream entity = new Datastream(); + entity.setName(tuple.get(qInstance.name)); + entity.setDescription(tuple.get(qInstance.description)); + J entityId = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (entityId != null) { + entity.setId(entityFactories.idFromObject(entityId)); + } + entity.setObservationType(tuple.get(qInstance.observationType)); + String observedArea = tuple.get(qInstance.observedArea.asText()); + if (observedArea != null) { + try { + Polygon polygon = GeoHelper.parsePolygon(observedArea); + entity.setObservedArea(polygon); + } catch (IllegalArgumentException e) { + // It's not a polygon, probably a point or a line. + } + } + ObservedProperty op = entityFactories.observedProperyFromId(tuple, qInstance.getObsPropertyId()); + entity.setObservedProperty(op); + Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); + Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); + if (pTimeStart != null && pTimeEnd != null) { + entity.setPhenomenonTime(Utils.intervalFromTimes(pTimeStart, pTimeEnd)); + } + Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); + Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); + if (rTimeStart != null && rTimeEnd != null) { + entity.setResultTime(Utils.intervalFromTimes(rTimeStart, rTimeEnd)); + } + if (select.isEmpty() || select.contains(EntityProperty.PROPERTIES)) { + String props = tuple.get(qInstance.properties); + entity.setProperties(Utils.jsonToObject(props, Map.class)); + } + entity.setSensor(entityFactories.sensorFromId(tuple, qInstance.getSensorId())); + entity.setThing(entityFactories.thingFromId(tuple, qInstance.getThingId())); + entity.setUnitOfMeasurement(new UnitOfMeasurement(tuple.get(qInstance.unitName), tuple.get(qInstance.unitSymbol), tuple.get(qInstance.unitDefinition))); + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, Datastream ds) throws NoSuchEntityException, IncompleteEntityException { + // First check ObservedPropery, Sensor and Thing + ObservedProperty op = ds.getObservedProperty(); + entityFactories.entityExistsOrCreate(pm, op); + + Sensor s = ds.getSensor(); + entityFactories.entityExistsOrCreate(pm, s); + + Thing t = ds.getThing(); + entityFactories.entityExistsOrCreate(pm, t); + + SQLQueryFactory qFactory = pm.createQueryFactory(); + + AbstractQDatastreams qd = qCollection.qDatastreams; + SQLInsertClause insert = qFactory.insert(qd); + insert.set(qd.name, ds.getName()); + insert.set(qd.description, ds.getDescription()); + insert.set(qd.observationType, ds.getObservationType()); + insert.set(qd.unitDefinition, ds.getUnitOfMeasurement().getDefinition()); + insert.set(qd.unitName, ds.getUnitOfMeasurement().getName()); + insert.set(qd.unitSymbol, ds.getUnitOfMeasurement().getSymbol()); + insert.set(qd.properties, EntityFactories.objectToJson(ds.getProperties())); + + insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManager.DATETIME_MAX.getMillis())); + insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManager.DATETIME_MIN.getMillis())); + insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManager.DATETIME_MAX.getMillis())); + insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManager.DATETIME_MIN.getMillis())); + + insert.set(qd.getObsPropertyId(), (J) op.getId().getValue()); + insert.set(qd.getSensorId(), (J) s.getId().getValue()); + insert.set(qd.getThingId(), (J) t.getId().getValue()); + + entityFactories.insertUserDefinedId(pm, insert, qd.getId(), ds); + + J datastreamId = insert.executeWithKey(qd.getId()); + LOGGER.debug("Inserted datastream. Created id = {}.", datastreamId); + ds.setId(entityFactories.idFromObject(datastreamId)); + + // Create Observations, if any. + for (Observation o : ds.getObservations()) { + o.setDatastream(new Datastream(ds.getId())); + o.complete(); + pm.insert(o); + } + + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, Datastream datastream, J dsId) throws NoSuchEntityException, IncompleteEntityException { + + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQDatastreams qd = qCollection.qDatastreams; + + SQLUpdateClause update = qFactory.update(qd); + EntityChangedMessage message = new EntityChangedMessage(); + + updateName(datastream, update, qd, message); + updateDescription(datastream, update, qd, message); + updateObservationType(datastream, update, qd, message); + updateProperties(datastream, update, qd, message); + updateObservedProperty(datastream, pm, update, qd, message); + updateSensor(datastream, pm, update, qd, message); + updateThing(datastream, pm, update, qd, message); + updateUnitOfMeasurement(datastream, update, qd, message); + + update.where(qd.getId().eq(dsId)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating Datastream {} caused {} rows to change!", dsId, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + + linkExistingObservations(datastream, pm, qFactory, dsId); + + LOGGER.debug("Updated Datastream {}", dsId); + return message; + } + + private void updateUnitOfMeasurement(Datastream datastream, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) throws IncompleteEntityException { + if (datastream.isSetUnitOfMeasurement()) { + if (datastream.getUnitOfMeasurement() == null) { + throw new IncompleteEntityException("unitOfMeasurement" + CAN_NOT_BE_NULL); + } + UnitOfMeasurement uom = datastream.getUnitOfMeasurement(); + update.set(qd.unitDefinition, uom.getDefinition()); + update.set(qd.unitName, uom.getName()); + update.set(qd.unitSymbol, uom.getSymbol()); + message.addField(EntityProperty.UNITOFMEASUREMENT); + } + } + + private void updateThing(Datastream datastream, PostgresPersistenceManager pm, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) throws NoSuchEntityException { + if (datastream.isSetThing()) { + if (!entityFactories.entityExists(pm, datastream.getThing())) { + throw new NoSuchEntityException("Thing with no id or not found."); + } + update.set(qd.getThingId(), (J) datastream.getThing().getId().getValue()); + message.addField(NavigationProperty.THING); + } + } + + private void updateSensor(Datastream datastream, PostgresPersistenceManager pm, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) throws NoSuchEntityException { + if (datastream.isSetSensor()) { + if (!entityFactories.entityExists(pm, datastream.getSensor())) { + throw new NoSuchEntityException("Sensor with no id or not found."); + } + update.set(qd.getSensorId(), (J) datastream.getSensor().getId().getValue()); + message.addField(NavigationProperty.SENSOR); + } + } + + private void updateObservedProperty(Datastream datastream, PostgresPersistenceManager pm, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) throws NoSuchEntityException { + if (datastream.isSetObservedProperty()) { + if (!entityFactories.entityExists(pm, datastream.getObservedProperty())) { + throw new NoSuchEntityException("ObservedProperty with no id or not found."); + } + update.set(qd.getObsPropertyId(), (J) datastream.getObservedProperty().getId().getValue()); + message.addField(NavigationProperty.OBSERVEDPROPERTY); + } + } + + private void updateProperties(Datastream datastream, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) { + if (datastream.isSetProperties()) { + update.set(qd.properties, EntityFactories.objectToJson(datastream.getProperties())); + message.addField(EntityProperty.PROPERTIES); + } + } + + private void updateObservationType(Datastream datastream, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) throws IncompleteEntityException { + if (datastream.isSetObservationType()) { + if (datastream.getObservationType() == null) { + throw new IncompleteEntityException("observationType" + CAN_NOT_BE_NULL); + } + update.set(qd.observationType, datastream.getObservationType()); + message.addField(EntityProperty.OBSERVATIONTYPE); + } + } + + private void updateDescription(Datastream datastream, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) throws IncompleteEntityException { + if (datastream.isSetDescription()) { + if (datastream.getDescription() == null) { + throw new IncompleteEntityException(EntityProperty.DESCRIPTION.jsonName + CAN_NOT_BE_NULL); + } + update.set(qd.description, datastream.getDescription()); + message.addField(EntityProperty.DESCRIPTION); + } + } + + private void updateName(Datastream d, SQLUpdateClause update, AbstractQDatastreams qd, EntityChangedMessage message) throws IncompleteEntityException { + if (d.isSetName()) { + if (d.getName() == null) { + throw new IncompleteEntityException("name" + CAN_NOT_BE_NULL); + } + update.set(qd.name, d.getName()); + message.addField(EntityProperty.NAME); + } + } + + private void linkExistingObservations(Datastream d, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J dsId) throws NoSuchEntityException { + for (Observation o : d.getObservations()) { + if (o.getId() == null || !entityFactories.entityExists(pm, o)) { + throw new NoSuchEntityException(EntityType.OBSERVATION.entityName + NO_ID_OR_NOT_FOUND); + } + J obsId = (J) o.getId().getValue(); + AbstractQObservations qo = qCollection.qObservations; + long oCount = qFactory.update(qo) + .set(qo.getDatastreamId(), dsId) + .where(qo.getId().eq(obsId)) + .execute(); + if (oCount > 0) { + LOGGER.debug("Assigned datastream {} to Observation {}.", dsId, obsId); + } + } + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("Datastream " + entityId + " not found."); + } + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + + @Override + public EntityType getEntityType() { + return EntityType.DATASTREAM; + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/EntityFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/EntityFactory.java new file mode 100644 index 000000000..2737ea785 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/EntityFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.core.Entity; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; + +/** + * + * @author scf + * @param The entity type this factory handles. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public interface EntityFactory & Path, J> { + + /** + * Creates a T, reading the Tuple with a qObject using no alias. + * + * @param tuple The tuple to create the Entity from. + * @param query The query used to request the data. + * @param dataSize The counter for the data size. This counts only the + * variable-sided elements, such as Observation.result and Thing.properties. + * @return The Entity created from the Tuple. + */ + public T create(Tuple tuple, Query query, DataSize dataSize); + + /** + * Insert the given entity into the database as a new entity. + * + * @param pm The persistenceManager to use to access the database. + * @param entity The entity to insert into the database. + * @return True if the entity was inserted successfully. + * @throws NoSuchEntityException If the entity depends on another entity + * that does not exist. + * @throws IncompleteEntityException If the entity is not complete and can + * thus not be inserted. + */ + public boolean insert(PostgresPersistenceManager pm, T entity) throws NoSuchEntityException, IncompleteEntityException; + + /** + * Update the given entity in the database. + * + * @param pm The persistenceManager to use to access the database. + * @param entity The updated entity. + * @param entityId The id of the entity to update. + * @return The message with the details about what was updated. + * @throws NoSuchEntityException If the update can not happen because a + * related entity is missing. + * @throws IncompleteEntityException If the update can not happen because + * required properties are missing. + */ + public EntityChangedMessage update(PostgresPersistenceManager pm, T entity, J entityId) throws NoSuchEntityException, IncompleteEntityException; + + /** + * Delete the entity with the given id. + * + * @param pm The persistenceManager to use to access the database. + * @param entityId The id of the entity to delete. + * @throws NoSuchEntityException If there was no entity with the given id. + */ + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException; + + /** + * Get the primary key of the table of the entity this factory + * + * @return The primary key of the table of the entity this factory creates, + * using no alias. + */ + public I getPrimaryKey(); + + /** + * Get the EntityType of the Entities created by this factory. + * + * @return The EntityType of the Entities created by this factory. + */ + public EntityType getEntityType(); + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/FeatureOfInterestFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/FeatureOfInterestFactory.java new file mode 100644 index 000000000..c4c3f17db --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/FeatureOfInterestFactory.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.NO_ID_OR_NOT_FOUND; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQFeatures; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class FeatureOfInterestFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(FeatureOfInterestFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQFeatures qInstance; + private final QCollection qCollection; + + public FeatureOfInterestFactory(EntityFactories factories, AbstractQFeatures qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public FeatureOfInterest create(Tuple tuple, Query query, DataSize dataSize) { + Set select = query == null ? Collections.emptySet() : query.getSelect(); + FeatureOfInterest entity = new FeatureOfInterest(); + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + entity.setName(tuple.get(qInstance.name)); + entity.setDescription(tuple.get(qInstance.description)); + String encodingType = tuple.get(qInstance.encodingType); + entity.setEncodingType(encodingType); + if (select.isEmpty() || select.contains(EntityProperty.FEATURE)) { + String locationString = tuple.get(qInstance.feature); + dataSize.increase(locationString == null ? 0 : locationString.length()); + entity.setFeature(Utils.locationFromEncoding(encodingType, locationString)); + } + if (select.isEmpty() || select.contains(EntityProperty.PROPERTIES)) { + String props = tuple.get(qInstance.properties); + entity.setProperties(Utils.jsonToObject(props, Map.class)); + } + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, FeatureOfInterest foi) throws IncompleteEntityException { + // No linked entities to check first. + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQFeatures qfoi = qCollection.qFeatures; + SQLInsertClause insert = qFactory.insert(qfoi); + insert.set(qfoi.name, foi.getName()); + insert.set(qfoi.description, foi.getDescription()); + insert.set(qfoi.properties, EntityFactories.objectToJson(foi.getProperties())); + + String encodingType = foi.getEncodingType(); + insert.set(qfoi.encodingType, encodingType); + EntityFactories.insertGeometry(insert, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); + + entityFactories.insertUserDefinedId(pm, insert, qfoi.getId(), foi); + + J generatedId = insert.executeWithKey(qfoi.getId()); + LOGGER.debug("Inserted FeatureOfInterest. Created id = {}.", generatedId); + foi.setId(entityFactories.idFromObject(generatedId)); + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, FeatureOfInterest foi, J foiId) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQFeatures qfoi = qCollection.qFeatures; + SQLUpdateClause update = qFactory.update(qfoi); + EntityChangedMessage message = new EntityChangedMessage(); + + updateName(foi, update, qfoi, message); + updateDescription(foi, update, qfoi, message); + updateProperties(foi, update, qfoi, message); + updateFeatureAndEncoding(foi, update, qfoi, message, qFactory, foiId); + + update.where(qfoi.getId().eq(foiId)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating FeatureOfInterest {} caused {} rows to change!", foiId, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + + linkExistingObservations(foi, pm, qFactory, foiId); + + LOGGER.debug("Updated FeatureOfInterest {}", foiId); + return message; + } + + private void updateName(FeatureOfInterest foi, SQLUpdateClause update, AbstractQFeatures qfoi, EntityChangedMessage message) throws IncompleteEntityException { + if (foi.isSetName()) { + if (foi.getName() == null) { + throw new IncompleteEntityException("name" + CAN_NOT_BE_NULL); + } + update.set(qfoi.name, foi.getName()); + message.addField(EntityProperty.NAME); + } + } + + private void updateDescription(FeatureOfInterest foi, SQLUpdateClause update, AbstractQFeatures qfoi, EntityChangedMessage message) throws IncompleteEntityException { + if (foi.isSetDescription()) { + if (foi.getDescription() == null) { + throw new IncompleteEntityException(EntityProperty.DESCRIPTION.jsonName + CAN_NOT_BE_NULL); + } + update.set(qfoi.description, foi.getDescription()); + message.addField(EntityProperty.DESCRIPTION); + } + } + + private void updateProperties(FeatureOfInterest foi, SQLUpdateClause update, AbstractQFeatures qfoi, EntityChangedMessage message) { + if (foi.isSetProperties()) { + update.set(qfoi.properties, EntityFactories.objectToJson(foi.getProperties())); + message.addField(EntityProperty.PROPERTIES); + } + } + + private void updateFeatureAndEncoding(FeatureOfInterest foi, SQLUpdateClause update, AbstractQFeatures qfoi, EntityChangedMessage message, SQLQueryFactory qFactory, J foiId) throws IncompleteEntityException { + if (foi.isSetEncodingType() && foi.getEncodingType() == null) { + throw new IncompleteEntityException("encodingType" + CAN_NOT_BE_NULL); + } + if (foi.isSetFeature() && foi.getFeature() == null) { + throw new IncompleteEntityException("feature" + CAN_NOT_BE_NULL); + } + if (foi.isSetEncodingType() && foi.getEncodingType() != null && foi.isSetFeature() && foi.getFeature() != null) { + String encodingType = foi.getEncodingType(); + update.set(qfoi.encodingType, encodingType); + EntityFactories.insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, foi.getFeature()); + message.addField(EntityProperty.ENCODINGTYPE); + message.addField(EntityProperty.FEATURE); + } else if (foi.isSetEncodingType() && foi.getEncodingType() != null) { + String encodingType = foi.getEncodingType(); + update.set(qfoi.encodingType, encodingType); + message.addField(EntityProperty.ENCODINGTYPE); + } else if (foi.isSetFeature() && foi.getFeature() != null) { + String encodingType = qFactory.select(qfoi.encodingType) + .from(qfoi) + .where(qfoi.getId().eq(foiId)) + .fetchFirst(); + Object parsedObject = EntityFactories.reParseGeometry(encodingType, foi.getFeature()); + EntityFactories.insertGeometry(update, qfoi.feature, qfoi.geom, encodingType, parsedObject); + message.addField(EntityProperty.FEATURE); + } + } + + private void linkExistingObservations(FeatureOfInterest foi, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J foiId) throws NoSuchEntityException { + // Link existing Observations to the FeatureOfInterest. + for (Observation o : foi.getObservations()) { + if (o.getId() == null || !entityFactories.entityExists(pm, o)) { + throw new NoSuchEntityException(EntityType.OBSERVATION.entityName + NO_ID_OR_NOT_FOUND); + } + J obsId = (J) o.getId().getValue(); + AbstractQObservations qo = qCollection.qObservations; + long oCount = qFactory.update(qo) + .set(qo.getFeatureId(), foiId) + .where(qo.getId().eq(obsId)) + .execute(); + if (oCount > 0) { + LOGGER.debug("Assigned FeatureOfInterest {} to Observation {}.", foiId, obsId); + } + } + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("FeatureOfInterest " + entityId + " not found."); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.FEATUREOFINTEREST; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/HistoricalLocationFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/HistoricalLocationFactory.java new file mode 100644 index 000000000..a62b04bd7 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/HistoricalLocationFactory.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; +import de.fraunhofer.iosb.ilt.sta.model.Location; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.LINKED_L_TO_HL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.LINKED_L_TO_T; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.UNLINKED_L_FROM_T; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.insertTimeInstant; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocationsHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.sql.Timestamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class HistoricalLocationFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(HistoricalLocationFactory.class); + private final EntityFactories entityFactories; + private final AbstractQHistLocations qInstance; + private final QCollection qCollection; + + public HistoricalLocationFactory(EntityFactories factories, AbstractQHistLocations qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public HistoricalLocation create(Tuple tuple, Query query, DataSize dataSize) { + HistoricalLocation entity = new HistoricalLocation(); + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + entity.setThing(entityFactories.thingFromId(tuple, qInstance.getThingId())); + entity.setTime(Utils.instantFromTime(tuple.get(qInstance.time))); + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, HistoricalLocation h) throws NoSuchEntityException, IncompleteEntityException { + Thing t = h.getThing(); + entityFactories.entityExistsOrCreate(pm, t); + J thingId = (J) h.getThing().getId().getValue(); + + Timestamp newTime = new Timestamp(h.getTime().getDateTime().getMillis()); + + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQHistLocations qhl = qCollection.qHistLocations; + SQLInsertClause insert = qFactory.insert(qhl); + insert.set(qhl.time, newTime); + insert.set(qhl.getThingId(), thingId); + + entityFactories.insertUserDefinedId(pm, insert, qhl.getId(), h); + + J generatedId = insert.executeWithKey(qhl.getId()); + LOGGER.debug("Inserted HistoricalLocation. Created id = {}.", generatedId); + h.setId(entityFactories.idFromObject(generatedId)); + + EntitySet locations = h.getLocations(); + for (Location l : locations) { + entityFactories.entityExistsOrCreate(pm, l); + J lId = (J) l.getId().getValue(); + AbstractQLocationsHistLocations qlhl = qCollection.qLocationsHistLocations; + insert = qFactory.insert(qlhl); + insert.set(qlhl.getHistLocationId(), generatedId); + insert.set(qlhl.getLocationId(), lId); + insert.execute(); + LOGGER.debug(LINKED_L_TO_HL, lId, generatedId); + } + + // 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. + Tuple lastHistLocation = qFactory.select(qhl.all()) + .from(qhl) + .where(qhl.getThingId().eq(thingId).and(qhl.time.gt(newTime))) + .orderBy(qhl.time.desc()) + .limit(1).fetchFirst(); + if (lastHistLocation == null) { + // We are the newest. + // Unlink old Locations from Thing. + AbstractQThingsLocations qtl = qCollection.qThingsLocations; + long count = qFactory.delete(qtl).where(qtl.getThingId().eq(thingId)).execute(); + LOGGER.debug(UNLINKED_L_FROM_T, count, thingId); + + // Link new locations to Thing, track the ids. + for (Location l : h.getLocations()) { + if (l.getId() == null || !entityFactories.entityExists(pm, l)) { + throw new NoSuchEntityException("Location with no id."); + } + J locationId = (J) l.getId().getValue(); + + qFactory.insert(qtl) + .set(qtl.getThingId(), thingId) + .set(qtl.getLocationId(), locationId) + .execute(); + LOGGER.debug(LINKED_L_TO_T, locationId, thingId); + } + } + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, HistoricalLocation hl, J id) throws IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQHistLocations qhl = qCollection.qHistLocations; + SQLUpdateClause update = qFactory.update(qhl); + EntityChangedMessage message = new EntityChangedMessage(); + + if (hl.isSetThing()) { + if (!entityFactories.entityExists(pm, hl.getThing())) { + throw new IncompleteEntityException("Thing" + CAN_NOT_BE_NULL); + } + update.set(qhl.getThingId(), (J) hl.getThing().getId().getValue()); + message.addField(NavigationProperty.THING); + } + if (hl.isSetTime()) { + if (hl.getTime() == null) { + throw new IncompleteEntityException("time" + CAN_NOT_BE_NULL); + } + insertTimeInstant(update, qhl.time, hl.getTime()); + message.addField(EntityProperty.TIME); + } + update.where(qhl.getId().eq(id)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating Location {} caused {} rows to change!", id, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + LOGGER.debug("Updated Location {}", id); + + // Link existing locations to the HistoricalLocation. + for (Location l : hl.getLocations()) { + if (!entityFactories.entityExists(pm, l)) { + throw new IllegalArgumentException("Unknown Location or Location with no id."); + } + J lId = (J) l.getId().getValue(); + + AbstractQLocationsHistLocations qlhl = qCollection.qLocationsHistLocations; + SQLInsertClause insert = qFactory.insert(qlhl); + insert.set(qlhl.getHistLocationId(), id); + insert.set(qlhl.getLocationId(), lId); + insert.execute(); + LOGGER.debug(LINKED_L_TO_HL, lId, id); + } + return message; + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("HistoricalLocation " + entityId + " not found."); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.HISTORICALLOCATION; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/LocationFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/LocationFactory.java new file mode 100644 index 000000000..c02ed44a0 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/LocationFactory.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLExpressions; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.HistoricalLocation; +import de.fraunhofer.iosb.ilt.sta.model.Location; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CREATED_HL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.LINKED_L_TO_HL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.LINKED_L_TO_T; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.UNLINKED_L_FROM_T; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocationsHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class LocationFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(LocationFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQLocations qInstance; + private final QCollection qCollection; + + public LocationFactory(EntityFactories factories, AbstractQLocations qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public Location create(Tuple tuple, Query query, DataSize dataSize) { + Set select = query == null ? Collections.emptySet() : query.getSelect(); + Location entity = new Location(); + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + entity.setName(tuple.get(qInstance.name)); + entity.setDescription(tuple.get(qInstance.description)); + String encodingType = tuple.get(qInstance.encodingType); + entity.setEncodingType(encodingType); + if (select.isEmpty() || select.contains(EntityProperty.LOCATION)) { + String locationString = tuple.get(qInstance.location); + dataSize.increase(locationString == null ? 0 : locationString.length()); + entity.setLocation(Utils.locationFromEncoding(encodingType, locationString)); + } + if (select.isEmpty() || select.contains(EntityProperty.PROPERTIES)) { + String props = tuple.get(qInstance.properties); + entity.setProperties(Utils.jsonToObject(props, Map.class)); + } + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, Location l) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQLocations ql = qCollection.qLocations; + SQLInsertClause insert = qFactory.insert(ql); + insert.set(ql.name, l.getName()); + insert.set(ql.description, l.getDescription()); + insert.set(ql.properties, EntityFactories.objectToJson(l.getProperties())); + + String encodingType = l.getEncodingType(); + insert.set(ql.encodingType, encodingType); + EntityFactories.insertGeometry(insert, ql.location, ql.geom, encodingType, l.getLocation()); + + entityFactories.insertUserDefinedId(pm, insert, ql.getId(), l); + + J locationId = insert.executeWithKey(ql.getId()); + LOGGER.debug("Inserted Location. Created id = {}.", locationId); + l.setId(entityFactories.idFromObject(locationId)); + + // Link Things + EntitySet things = l.getThings(); + for (Thing t : things) { + entityFactories.entityExistsOrCreate(pm, t); + linkThingToLocation(qFactory, t, locationId); + } + + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, Location location, J locationId) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQLocations ql = qCollection.qLocations; + SQLUpdateClause update = qFactory.update(ql); + EntityChangedMessage message = new EntityChangedMessage(); + + updateName(location, update, ql, message); + updateDescription(location, update, ql, message); + updateProperties(location, update, ql, message); + updateLocationAndEncoding(location, locationId, update, ql, message, qFactory); + + update.where(ql.getId().eq(locationId)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating Location {} caused {} rows to change!", locationId, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + LOGGER.debug("Updated Location {}", locationId); + + linkHistoricalLocations(location, qFactory, locationId); + linkThings(location, pm, qFactory, locationId); + + return message; + } + + private void updateName(Location location, SQLUpdateClause update, AbstractQLocations ql, EntityChangedMessage message) throws IncompleteEntityException { + if (location.isSetName()) { + if (location.getName() == null) { + throw new IncompleteEntityException("name" + CAN_NOT_BE_NULL); + } + update.set(ql.name, location.getName()); + message.addField(EntityProperty.NAME); + } + } + + private void updateDescription(Location location, SQLUpdateClause update, AbstractQLocations ql, EntityChangedMessage message) throws IncompleteEntityException { + if (location.isSetDescription()) { + if (location.getDescription() == null) { + throw new IncompleteEntityException(EntityProperty.DESCRIPTION.jsonName + CAN_NOT_BE_NULL); + } + update.set(ql.description, location.getDescription()); + message.addField(EntityProperty.DESCRIPTION); + } + } + + private void updateProperties(Location location, SQLUpdateClause update, AbstractQLocations ql, EntityChangedMessage message) { + if (location.isSetProperties()) { + update.set(ql.properties, EntityFactories.objectToJson(location.getProperties())); + message.addField(EntityProperty.PROPERTIES); + } + } + + private void updateLocationAndEncoding(Location location, J locationId, SQLUpdateClause update, AbstractQLocations ql, EntityChangedMessage message, SQLQueryFactory qFactory) throws IncompleteEntityException { + if (location.isSetEncodingType() && location.getEncodingType() == null) { + throw new IncompleteEntityException("encodingType" + CAN_NOT_BE_NULL); + } + if (location.isSetLocation() && location.getLocation() == null) { + throw new IncompleteEntityException("locations" + CAN_NOT_BE_NULL); + } + if (location.isSetEncodingType() && location.getEncodingType() != null && location.isSetLocation() && location.getLocation() != null) { + String encodingType = location.getEncodingType(); + update.set(ql.encodingType, encodingType); + EntityFactories.insertGeometry(update, ql.location, ql.geom, encodingType, location.getLocation()); + message.addField(EntityProperty.ENCODINGTYPE); + message.addField(EntityProperty.LOCATION); + } else if (location.isSetEncodingType() && location.getEncodingType() != null) { + String encodingType = location.getEncodingType(); + update.set(ql.encodingType, encodingType); + message.addField(EntityProperty.ENCODINGTYPE); + } else if (location.isSetLocation() && location.getLocation() != null) { + String encodingType = qFactory.select(ql.encodingType) + .from(ql) + .where(ql.getId().eq(locationId)) + .fetchFirst(); + Object parsedObject = EntityFactories.reParseGeometry(encodingType, location.getLocation()); + EntityFactories.insertGeometry(update, ql.location, ql.geom, encodingType, parsedObject); + message.addField(EntityProperty.LOCATION); + } + } + + private void linkThings(Location l, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J locationId) throws NoSuchEntityException { + EntitySet things = l.getThings(); + for (Thing t : things) { + if (!entityFactories.entityExists(pm, t)) { + throw new NoSuchEntityException("Thing not found."); + } + linkThingToLocation(qFactory, t, locationId); + } + } + + private void linkHistoricalLocations(Location l, SQLQueryFactory qFactory, J locationId) { + for (HistoricalLocation hl : l.getHistoricalLocations()) { + if (hl.getId() == null) { + throw new IllegalArgumentException("HistoricalLocation with no id."); + } + J hlId = (J) hl.getId().getValue(); + + AbstractQLocationsHistLocations qlhl = qCollection.qLocationsHistLocations; + SQLInsertClause insert = qFactory.insert(qlhl); + insert.set(qlhl.getHistLocationId(), hlId); + insert.set(qlhl.getLocationId(), locationId); + insert.execute(); + LOGGER.debug(LINKED_L_TO_HL, locationId, hlId); + } + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("Location " + entityId + " not found."); + } + LOGGER.debug("Deleted {} Locations", count); + // Also delete all historicalLocations that no longer reference any location + AbstractQHistLocations qhl = qCollection.qHistLocations; + AbstractQLocationsHistLocations qlhl = qCollection.qLocationsHistLocations; + count = pm.createQueryFactory() + .delete(qhl) + .where(qhl.getId().in( + SQLExpressions.select(qhl.getId()) + .from(qhl) + .leftJoin(qlhl).on(qhl.getId().eq(qlhl.getHistLocationId())) + .where(qlhl.getLocationId().isNull()) + )) + .execute(); + LOGGER.debug("Deleted {} HistoricalLocations", count); + } + + private void linkThingToLocation(SQLQueryFactory qFactory, Thing t, J locationId) { + J thingId = (J) t.getId().getValue(); + + // Unlink old Locations from Thing. + AbstractQThingsLocations qtl = qCollection.qThingsLocations; + long delCount = qFactory.delete(qtl).where(qtl.getThingId().eq(thingId)).execute(); + LOGGER.debug(UNLINKED_L_FROM_T, delCount, thingId); + + // Link new Location to thing. + SQLInsertClause linkInsert = qFactory.insert(qtl); + linkInsert.set(qtl.getThingId(), thingId); + linkInsert.set(qtl.getLocationId(), locationId); + linkInsert.execute(); + LOGGER.debug(LINKED_L_TO_T, locationId, thingId); + + // Create HistoricalLocation for Thing + AbstractQHistLocations qhl = qCollection.qHistLocations; + linkInsert = qFactory.insert(qhl); + linkInsert.set(qhl.getThingId(), thingId); + linkInsert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); + // TODO: maybe use histLocationId based on locationId + J histLocationId = linkInsert.executeWithKey(qhl.getId()); + LOGGER.debug(CREATED_HL, histLocationId); + + // Link Location to HistoricalLocation. + AbstractQLocationsHistLocations qlhl = qCollection.qLocationsHistLocations; + qFactory.insert(qlhl) + .set(qlhl.getHistLocationId(), histLocationId) + .set(qlhl.getLocationId(), locationId) + .execute(); + LOGGER.debug(LINKED_L_TO_HL, locationId, histLocationId); + } + + @Override + public EntityType getEntityType() { + return EntityType.LOCATION; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/MultiDatastreamFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/MultiDatastreamFactory.java new file mode 100644 index 000000000..27281f1c6 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/MultiDatastreamFactory.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; +import de.fraunhofer.iosb.ilt.sta.model.Sensor; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.model.core.EntitySet; +import de.fraunhofer.iosb.ilt.sta.model.ext.UnitOfMeasurement; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.NO_ID_OR_NOT_FOUND; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreamsObsProperties; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.GeoHelper; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.geojson.Polygon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class MultiDatastreamFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(MultiDatastreamFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQMultiDatastreams qInstance; + private final QCollection qCollection; + + public MultiDatastreamFactory(EntityFactories factories, AbstractQMultiDatastreams qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public MultiDatastream create(Tuple tuple, Query query, DataSize dataSize) { + Set select = query == null ? Collections.emptySet() : query.getSelect(); + MultiDatastream entity = new MultiDatastream(); + entity.setName(tuple.get(qInstance.name)); + entity.setDescription(tuple.get(qInstance.description)); + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + List observationTypes = Utils.jsonToObject(tuple.get(qInstance.observationTypes), EntityFactories.TYPE_LIST_STRING); + entity.setMultiObservationDataTypes(observationTypes); + String observedArea = tuple.get(qInstance.observedArea.asText()); + if (observedArea != null) { + try { + Polygon polygon = GeoHelper.parsePolygon(observedArea); + entity.setObservedArea(polygon); + } catch (IllegalArgumentException e) { + // It's not a polygon, probably a point or a line. + } + } + Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); + Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); + if (pTimeStart != null && pTimeEnd != null) { + entity.setPhenomenonTime(Utils.intervalFromTimes(pTimeStart, pTimeEnd)); + } + Timestamp rTimeEnd = tuple.get(qInstance.resultTimeEnd); + Timestamp rTimeStart = tuple.get(qInstance.resultTimeStart); + if (rTimeStart != null && rTimeEnd != null) { + entity.setResultTime(Utils.intervalFromTimes(rTimeStart, rTimeEnd)); + } + if (select.isEmpty() || select.contains(EntityProperty.PROPERTIES)) { + String props = tuple.get(qInstance.properties); + entity.setProperties(Utils.jsonToObject(props, Map.class)); + } + entity.setSensor(entityFactories.sensorFromId(tuple, qInstance.getSensorId())); + entity.setThing(entityFactories.thingFromId(tuple, qInstance.getThingId())); + List units = Utils.jsonToObject(tuple.get(qInstance.unitOfMeasurements), EntityFactories.TYPE_LIST_UOM); + entity.setUnitOfMeasurements(units); + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, MultiDatastream ds) throws NoSuchEntityException, IncompleteEntityException { + // First check Sensor and Thing + Sensor s = ds.getSensor(); + entityFactories.entityExistsOrCreate(pm, s); + + Thing t = ds.getThing(); + entityFactories.entityExistsOrCreate(pm, t); + + SQLQueryFactory qFactory = pm.createQueryFactory(); + + AbstractQMultiDatastreams qd = qCollection.qMultiDatastreams; + SQLInsertClause insert = qFactory.insert(qd); + insert.set(qd.name, ds.getName()); + insert.set(qd.description, ds.getDescription()); + insert.set(qd.observationTypes, EntityFactories.objectToJson(ds.getMultiObservationDataTypes())); + insert.set(qd.unitOfMeasurements, EntityFactories.objectToJson(ds.getUnitOfMeasurements())); + insert.set(qd.properties, EntityFactories.objectToJson(ds.getProperties())); + + insert.set(qd.phenomenonTimeStart, new Timestamp(PostgresPersistenceManager.DATETIME_MAX.getMillis())); + insert.set(qd.phenomenonTimeEnd, new Timestamp(PostgresPersistenceManager.DATETIME_MIN.getMillis())); + insert.set(qd.resultTimeStart, new Timestamp(PostgresPersistenceManager.DATETIME_MAX.getMillis())); + insert.set(qd.resultTimeEnd, new Timestamp(PostgresPersistenceManager.DATETIME_MIN.getMillis())); + + insert.set(qd.getSensorId(), (J) s.getId().getValue()); + insert.set(qd.getThingId(), (J) t.getId().getValue()); + + entityFactories.insertUserDefinedId(pm, insert, qd.getId(), ds); + + J multiDatastreamId = insert.executeWithKey(qd.getId()); + LOGGER.debug("Inserted multiDatastream. Created id = {}.", multiDatastreamId); + ds.setId(entityFactories.idFromObject(multiDatastreamId)); + + // Create new Locations, if any. + EntitySet ops = ds.getObservedProperties(); + int rank = 0; + for (ObservedProperty op : ops) { + entityFactories.entityExistsOrCreate(pm, op); + J opId = (J) op.getId().getValue(); + + AbstractQMultiDatastreamsObsProperties qMdOp = qCollection.qMultiDatastreamsObsProperties; + insert = qFactory.insert(qMdOp); + insert.set(qMdOp.getMultiDatastreamId(), multiDatastreamId); + insert.set(qMdOp.getObsPropertyId(), opId); + insert.set(qMdOp.rank, rank); + insert.execute(); + LOGGER.debug("Linked MultiDatastream {} to ObservedProperty {} with rank {}.", multiDatastreamId, opId, rank); + rank++; + } + + // Create Observations, if any. + for (Observation o : ds.getObservations()) { + o.setMultiDatastream(new MultiDatastream(ds.getId())); + o.complete(); + pm.insert(o); + } + + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, MultiDatastream md, J mdsId) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQMultiDatastreams qmd = qCollection.qMultiDatastreams; + SQLUpdateClause update = qFactory.update(qmd); + EntityChangedMessage message = new EntityChangedMessage(); + + updateName(md, update, qmd, message); + updateDescription(md, update, qmd, message); + updateProperties(md, update, qmd, message); + + updateSensor(md, pm, update, qmd, message); + updateThing(md, pm, update, qmd, message); + + MultiDatastream original = (MultiDatastream) pm.get(EntityType.MULTIDATASTREAM, entityFactories.idFromObject(mdsId)); + int countOrig = original.getMultiObservationDataTypes().size(); + + int countUom = updateUnitsOfMeasure(countOrig, md, update, qmd, message); + int countDataTypes = updateDataTypes(countOrig, md, update, qmd, message); + + EntitySet ops = md.getObservedProperties(); + int countOps = countOrig + ops.size(); + + if (countUom != countDataTypes) { + throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of multiObservationDataTypes."); + } + if (countUom != countOps) { + throw new IllegalArgumentException("New number of unitOfMeasurements does not match new number of ObservedProperties."); + } + + update.where(qmd.getId().eq(mdsId)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating Datastream {} caused {} rows to change!", mdsId, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + + linkExistingObservedProperties(mdsId, countOrig, ops, qFactory, pm); + + linkExistingObservations(md, pm, qFactory, mdsId); + + LOGGER.debug("Updated multiDatastream {}", mdsId); + return message; + } + + private void updateName(MultiDatastream md, SQLUpdateClause update, AbstractQMultiDatastreams qmd, EntityChangedMessage message) throws IncompleteEntityException { + if (md.isSetName()) { + if (md.getName() == null) { + throw new IncompleteEntityException("name" + CAN_NOT_BE_NULL); + } + update.set(qmd.name, md.getName()); + message.addField(EntityProperty.NAME); + } + } + + private void updateDescription(MultiDatastream md, SQLUpdateClause update, AbstractQMultiDatastreams qmd, EntityChangedMessage message) throws IncompleteEntityException { + if (md.isSetDescription()) { + if (md.getDescription() == null) { + throw new IncompleteEntityException(EntityProperty.DESCRIPTION.jsonName + CAN_NOT_BE_NULL); + } + update.set(qmd.description, md.getDescription()); + message.addField(EntityProperty.DESCRIPTION); + } + } + + private void updateProperties(MultiDatastream md, SQLUpdateClause update, AbstractQMultiDatastreams qmd, EntityChangedMessage message) { + if (md.isSetProperties()) { + update.set(qmd.properties, EntityFactories.objectToJson(md.getProperties())); + message.addField(EntityProperty.PROPERTIES); + } + } + + private void updateSensor(MultiDatastream md, PostgresPersistenceManager pm, SQLUpdateClause update, AbstractQMultiDatastreams qmd, EntityChangedMessage message) throws NoSuchEntityException { + if (md.isSetSensor()) { + if (!entityFactories.entityExists(pm, md.getSensor())) { + throw new NoSuchEntityException("Sensor with no id or not found."); + } + update.set(qmd.getSensorId(), (J) md.getSensor().getId().getValue()); + message.addField(NavigationProperty.SENSOR); + } + } + + private void updateThing(MultiDatastream md, PostgresPersistenceManager pm, SQLUpdateClause update, AbstractQMultiDatastreams qmd, EntityChangedMessage message) throws NoSuchEntityException { + if (md.isSetThing()) { + if (!entityFactories.entityExists(pm, md.getThing())) { + throw new NoSuchEntityException("Thing with no id or not found."); + } + update.set(qmd.getThingId(), (J) md.getThing().getId().getValue()); + message.addField(NavigationProperty.THING); + } + } + + private int updateUnitsOfMeasure(int countOrig, MultiDatastream md, SQLUpdateClause update, AbstractQMultiDatastreams qmd, EntityChangedMessage message) throws IncompleteEntityException { + int countUom = countOrig; + if (md.isSetUnitOfMeasurements()) { + if (md.getUnitOfMeasurements() == null) { + throw new IncompleteEntityException("unitOfMeasurements" + CAN_NOT_BE_NULL); + } + List uoms = md.getUnitOfMeasurements(); + countUom = uoms.size(); + update.set(qmd.unitOfMeasurements, EntityFactories.objectToJson(uoms)); + message.addField(EntityProperty.UNITOFMEASUREMENTS); + } + return countUom; + } + + private int updateDataTypes(int countOrig, MultiDatastream md, SQLUpdateClause update, AbstractQMultiDatastreams qmd, EntityChangedMessage message) throws IncompleteEntityException { + int countDataTypes = countOrig; + if (md.isSetMultiObservationDataTypes()) { + List dataTypes = md.getMultiObservationDataTypes(); + if (dataTypes == null) { + throw new IncompleteEntityException("multiObservationDataTypes" + CAN_NOT_BE_NULL); + } + countDataTypes = dataTypes.size(); + update.set(qmd.observationTypes, EntityFactories.objectToJson(dataTypes)); + message.addField(EntityProperty.MULTIOBSERVATIONDATATYPES); + } + return countDataTypes; + } + + private void linkExistingObservedProperties(J mdsId, int countOrig, EntitySet ops, SQLQueryFactory qFactory, PostgresPersistenceManager pm) throws NoSuchEntityException { + // Link existing ObservedProperties to the MultiDatastream. + int rank = countOrig; + for (ObservedProperty op : ops) { + if (op.getId() == null || !entityFactories.entityExists(pm, op)) { + throw new NoSuchEntityException("ObservedProperty with no id or not found."); + } + J opId = (J) op.getId().getValue(); + AbstractQMultiDatastreamsObsProperties qMdOp = qCollection.qMultiDatastreamsObsProperties; + long oCount = qFactory.insert(qMdOp) + .set(qMdOp.getMultiDatastreamId(), mdsId) + .set(qMdOp.getObsPropertyId(), opId) + .set(qMdOp.rank, rank) + .execute(); + if (oCount > 0) { + LOGGER.debug("Assigned datastream {} to ObservedProperty {} with rank {}.", mdsId, opId, rank); + } + rank++; + } + } + + private void linkExistingObservations(MultiDatastream md, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J mdsId) throws NoSuchEntityException { + // Link existing Observations to the MultiDatastream. + for (Observation o : md.getObservations()) { + if (o.getId() == null || !entityFactories.entityExists(pm, o)) { + throw new NoSuchEntityException(EntityType.OBSERVATION.entityName + NO_ID_OR_NOT_FOUND); + } + J obsId = (J) o.getId().getValue(); + AbstractQObservations qo = qCollection.qObservations; + long oCount = qFactory.update(qo) + .set(qo.getDatastreamId(), mdsId) + .where(qo.getId().eq(obsId)) + .execute(); + if (oCount > 0) { + LOGGER.debug("Assigned multiDatastream {} to Observation {}.", mdsId, obsId); + } + } + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("MultiDatastream " + entityId + " not found."); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.MULTIDATASTREAM; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ObservationFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ObservationFactory.java new file mode 100644 index 000000000..e0a65fd3c --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ObservationFactory.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.dml.StoreClause; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.FeatureOfInterest; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; +import de.fraunhofer.iosb.ilt.sta.model.Observation; +import de.fraunhofer.iosb.ilt.sta.model.core.Id; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeInstant; +import de.fraunhofer.iosb.ilt.sta.model.ext.TimeValue; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntitySetPathElement; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.NavigationProperty; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.path.ResourcePath; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.ResultType; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObservations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class ObservationFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(ObservationFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQObservations qInstance; + private final QCollection qCollection; + + public ObservationFactory(EntityFactories factories, AbstractQObservations qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public Observation create(Tuple tuple, Query query, DataSize dataSize) { + Observation entity = new Observation(); + Set select = query == null ? Collections.emptySet() : query.getSelect(); + + J dsId = entityFactories.getIdFromTuple(tuple, qInstance.getDatastreamId()); + if (dsId != null) { + entity.setDatastream(entityFactories.datastreamFromId(dsId)); + } + + J mDsId = entityFactories.getIdFromTuple(tuple, qInstance.getMultiDatastreamId()); + if (mDsId != null) { + entity.setMultiDatastream(entityFactories.multiDatastreamFromId(mDsId)); + } + + entity.setFeatureOfInterest(entityFactories.featureOfInterestFromId(tuple, qInstance.getFeatureId())); + + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + + if (select.isEmpty() || select.contains(EntityProperty.PARAMETERS)) { + String props = tuple.get(qInstance.parameters); + dataSize.increase(props == null ? 0 : props.length()); + entity.setParameters(Utils.jsonToObject(props, Map.class)); + } + + Timestamp pTimeStart = tuple.get(qInstance.phenomenonTimeStart); + Timestamp pTimeEnd = tuple.get(qInstance.phenomenonTimeEnd); + entity.setPhenomenonTime(Utils.valueFromTimes(pTimeStart, pTimeEnd)); + + readResultFromDb(tuple, entity, dataSize, select); + readResultQuality(select, tuple, dataSize, entity); + + entity.setResultTime(Utils.instantFromTime(tuple.get(qInstance.resultTime))); + Timestamp vTimeStart = tuple.get(qInstance.validTimeStart); + Timestamp vTimeEnd = tuple.get(qInstance.validTimeEnd); + if (vTimeStart != null && vTimeEnd != null) { + entity.setValidTime(Utils.intervalFromTimes(vTimeStart, vTimeEnd)); + } + return entity; + } + + private void readResultQuality(Set select, Tuple tuple, DataSize dataSize, Observation entity) { + if (select.isEmpty() || select.contains(EntityProperty.RESULTQUALITY)) { + String resultQuality = tuple.get(qInstance.resultQuality); + dataSize.increase(resultQuality == null ? 0 : resultQuality.length()); + entity.setResultQuality(Utils.jsonToObject(resultQuality, Object.class)); + } + } + + private void readResultFromDb(Tuple tuple, Observation entity, DataSize dataSize, Set select) { + if (!select.isEmpty() && !select.contains(EntityProperty.RESULT)) { + return; + } + Byte resultTypeOrd = tuple.get(qInstance.resultType); + if (resultTypeOrd != null) { + ResultType resultType = ResultType.fromSqlValue(resultTypeOrd); + switch (resultType) { + case BOOLEAN: + entity.setResult(tuple.get(qInstance.resultBoolean)); + break; + case NUMBER: + try { + entity.setResult(new BigDecimal(tuple.get(qInstance.resultString))); + } catch (NumberFormatException e) { + // It was not a Number? Use the double value. + entity.setResult(tuple.get(qInstance.resultNumber)); + } + break; + case OBJECT_ARRAY: + String jsonData = tuple.get(qInstance.resultJson); + dataSize.increase(jsonData == null ? 0 : jsonData.length()); + entity.setResult(Utils.jsonToTree(jsonData)); + break; + case STRING: + String stringData = tuple.get(qInstance.resultString); + dataSize.increase(stringData == null ? 0 : stringData.length()); + entity.setResult(stringData); + break; + } + } + } + + @Override + public boolean insert(PostgresPersistenceManager pm, Observation newObservation) throws NoSuchEntityException, IncompleteEntityException { + Datastream ds = newObservation.getDatastream(); + MultiDatastream mds = newObservation.getMultiDatastream(); + Id streamId; + boolean newIsMultiDatastream = false; + if (ds != null) { + entityFactories.entityExistsOrCreate(pm, ds); + streamId = ds.getId(); + } else if (mds != null) { + entityFactories.entityExistsOrCreate(pm, mds); + streamId = mds.getId(); + newIsMultiDatastream = true; + } else { + throw new IncompleteEntityException("Missing Datastream or MultiDatastream."); + } + + FeatureOfInterest f = newObservation.getFeatureOfInterest(); + if (f == null) { + f = entityFactories.generateFeatureOfInterest(pm, streamId, newIsMultiDatastream); + } else { + entityFactories.entityExistsOrCreate(pm, f); + } + + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQObservations qo = qCollection.qObservations; + SQLInsertClause query = qFactory.insert(qo); + + if (ds != null) { + query.set(qo.getDatastreamId(), (J) ds.getId().getValue()); + } + if (mds != null) { + query.set(qo.getMultiDatastreamId(), (J) mds.getId().getValue()); + } + + TimeValue phenomenonTime = newObservation.getPhenomenonTime(); + if (phenomenonTime == null) { + phenomenonTime = TimeInstant.now(); + } + EntityFactories.insertTimeValue(query, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, phenomenonTime); + EntityFactories.insertTimeInstant(query, qo.resultTime, newObservation.getResultTime()); + EntityFactories.insertTimeInterval(query, qo.validTimeStart, qo.validTimeEnd, newObservation.getValidTime()); + + handleResult(newObservation, newIsMultiDatastream, pm, query, qo); + + if (newObservation.getResultQuality() != null) { + query.set(qo.resultQuality, newObservation.getResultQuality().toString()); + } + query.set(qo.parameters, EntityFactories.objectToJson(newObservation.getParameters())); + query.set(qo.getFeatureId(), (J) f.getId().getValue()); + + entityFactories.insertUserDefinedId(pm, query, qo.getId(), newObservation); + + J generatedId = query.executeWithKey(qo.getId()); + LOGGER.debug("Inserted Observation. Created id = {}.", generatedId); + newObservation.setId(entityFactories.idFromObject(generatedId)); + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, Observation newObservation, J id) throws IncompleteEntityException { + Observation oldObservation = (Observation) pm.get(EntityType.OBSERVATION, entityFactories.idFromObject(id)); + + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQObservations qo = qCollection.qObservations; + SQLUpdateClause query = qFactory.update(qo); + EntityChangedMessage message = new EntityChangedMessage(); + + boolean newHasDatastream = checkDatastreamSet(newObservation, oldObservation, message, query, qo, pm); + boolean newIsMultiDatastream = checkMultiDatastreamSet(oldObservation, newObservation, message, query, qo, pm); + + if (newHasDatastream == newIsMultiDatastream) { + throw new IllegalArgumentException("Observation must have either a Datastream or a MultiDatastream."); + } + if (newObservation.isSetFeatureOfInterest()) { + if (!entityFactories.entityExists(pm, newObservation.getFeatureOfInterest())) { + throw new IncompleteEntityException("FeatureOfInterest not found."); + } + query.set(qo.getFeatureId(), (J) newObservation.getFeatureOfInterest().getId().getValue()); + message.addField(NavigationProperty.FEATUREOFINTEREST); + } + if (newObservation.isSetParameters()) { + query.set(qo.parameters, EntityFactories.objectToJson(newObservation.getParameters())); + message.addField(EntityProperty.PARAMETERS); + } + if (newObservation.isSetPhenomenonTime()) { + if (newObservation.getPhenomenonTime() == null) { + throw new IncompleteEntityException("phenomenonTime" + CAN_NOT_BE_NULL); + } + EntityFactories.insertTimeValue(query, qo.phenomenonTimeStart, qo.phenomenonTimeEnd, newObservation.getPhenomenonTime()); + message.addField(EntityProperty.PHENOMENONTIME); + } + + if (newObservation.isSetResult()) { + handleResult(newObservation, newIsMultiDatastream, pm, query, qo); + message.addField(EntityProperty.RESULT); + } + + if (newObservation.isSetResultQuality()) { + query.set(qo.resultQuality, EntityFactories.objectToJson(newObservation.getResultQuality())); + message.addField(EntityProperty.RESULTQUALITY); + } + if (newObservation.isSetResultTime()) { + EntityFactories.insertTimeInstant(query, qo.resultTime, newObservation.getResultTime()); + message.addField(EntityProperty.RESULTTIME); + } + if (newObservation.isSetValidTime()) { + EntityFactories.insertTimeInterval(query, qo.validTimeStart, qo.validTimeEnd, newObservation.getValidTime()); + message.addField(EntityProperty.VALIDTIME); + } + query.where(qo.getId().eq(id)); + long count = 0; + if (!query.isEmpty()) { + count = query.execute(); + } + if (count > 1) { + LOGGER.error("Updating Observation {} caused {} rows to change!", id, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + LOGGER.debug("Updated Observation {}", id); + return message; + } + + private void handleResult(Observation newObservation, boolean newIsMultiDatastream, PostgresPersistenceManager pm, StoreClause query, AbstractQObservations qo) { + Object result = newObservation.getResult(); + if (newIsMultiDatastream) { + MultiDatastream mds = newObservation.getMultiDatastream(); + if (!(result instanceof List)) { + throw new IllegalArgumentException("Multidatastream only accepts array results."); + } + List list = (List) result; + ResourcePath path = mds.getPath(); + path.addPathElement(new EntitySetPathElement(EntityType.OBSERVEDPROPERTY, null), false, false); + long count = pm.count(path, null); + if (count != list.size()) { + throw new IllegalArgumentException("Size of result array (" + list.size() + ") must match number of observed properties (" + count + ") in the MultiDatastream."); + } + } + + if (result instanceof Number) { + query.set(qo.resultType, ResultType.NUMBER.sqlValue()); + query.set(qo.resultString, result.toString()); + query.set(qo.resultNumber, ((Number) result).doubleValue()); + query.setNull(qo.resultBoolean); + query.setNull(qo.resultJson); + } else if (result instanceof Boolean) { + query.set(qo.resultType, ResultType.BOOLEAN.sqlValue()); + query.set(qo.resultString, result.toString()); + query.set(qo.resultBoolean, result); + query.setNull(qo.resultNumber); + query.setNull(qo.resultJson); + } else if (result instanceof String) { + query.set(qo.resultType, ResultType.STRING.sqlValue()); + query.set(qo.resultString, result.toString()); + query.setNull(qo.resultNumber); + query.setNull(qo.resultBoolean); + query.setNull(qo.resultJson); + } else { + query.set(qo.resultType, ResultType.OBJECT_ARRAY.sqlValue()); + query.set(qo.resultJson, EntityFactories.objectToJson(result)); + query.setNull(qo.resultString); + query.setNull(qo.resultNumber); + query.setNull(qo.resultBoolean); + } + } + + private boolean checkMultiDatastreamSet(Observation oldObservation, Observation newObservation, EntityChangedMessage message, SQLUpdateClause update, AbstractQObservations qo, PostgresPersistenceManager pm) throws IncompleteEntityException { + MultiDatastream mds = oldObservation.getMultiDatastream(); + boolean newHasMultiDatastream = mds != null; + if (newObservation.isSetMultiDatastream()) { + mds = newObservation.getMultiDatastream(); + if (mds == null) { + newHasMultiDatastream = false; + update.setNull(qo.getMultiDatastreamId()); + message.addField(NavigationProperty.MULTIDATASTREAM); + } else { + if (!entityFactories.entityExists(pm, mds)) { + throw new IncompleteEntityException("MultiDatastream not found."); + } + newHasMultiDatastream = true; + update.set(qo.getMultiDatastreamId(), (J) mds.getId().getValue()); + message.addField(NavigationProperty.MULTIDATASTREAM); + } + } + return newHasMultiDatastream; + } + + private boolean checkDatastreamSet(Observation newObservation, Observation oldObservation, EntityChangedMessage message, SQLUpdateClause update, AbstractQObservations qo, PostgresPersistenceManager pm) throws IncompleteEntityException { + Datastream ds = oldObservation.getDatastream(); + boolean newHasDatastream = ds != null; + if (newObservation.isSetDatastream()) { + if (newObservation.getDatastream() == null) { + newHasDatastream = false; + update.setNull(qo.getDatastreamId()); + message.addField(NavigationProperty.DATASTREAM); + } else { + if (!entityFactories.entityExists(pm, newObservation.getDatastream())) { + throw new IncompleteEntityException("Datastream not found."); + } + newHasDatastream = true; + ds = newObservation.getDatastream(); + update.set(qo.getDatastreamId(), (J) ds.getId().getValue()); + message.addField(NavigationProperty.DATASTREAM); + } + } + return newHasDatastream; + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("Observation " + entityId + " not found."); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.OBSERVATION; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ObservedPropertyFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ObservedPropertyFactory.java new file mode 100644 index 000000000..a682bccd2 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ObservedPropertyFactory.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLExpressions; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; +import de.fraunhofer.iosb.ilt.sta.model.ObservedProperty; +import de.fraunhofer.iosb.ilt.sta.model.Sensor; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.NO_ID_OR_NOT_FOUND; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreamsObsProperties; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQObsProperties; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class ObservedPropertyFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(ObservedPropertyFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQObsProperties qInstance; + private final QCollection qCollection; + + public ObservedPropertyFactory(EntityFactories factories, AbstractQObsProperties qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public ObservedProperty create(Tuple tuple, Query query, DataSize dataSize) { + Set select = query == null ? Collections.emptySet() : query.getSelect(); + ObservedProperty entity = new ObservedProperty(); + entity.setDefinition(tuple.get(qInstance.definition)); + entity.setDescription(tuple.get(qInstance.description)); + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + entity.setName(tuple.get(qInstance.name)); + if (select.isEmpty() || select.contains(EntityProperty.PROPERTIES)) { + String props = tuple.get(qInstance.properties); + entity.setProperties(Utils.jsonToObject(props, Map.class)); + } + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, ObservedProperty op) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQObsProperties qop = qCollection.qObsProperties; + SQLInsertClause insert = qFactory.insert(qop); + insert.set(qop.definition, op.getDefinition()); + insert.set(qop.name, op.getName()); + insert.set(qop.description, op.getDescription()); + insert.set(qop.properties, EntityFactories.objectToJson(op.getProperties())); + + entityFactories.insertUserDefinedId(pm, insert, qop.getId(), op); + + J generatedId = insert.executeWithKey(qop.getId()); + LOGGER.debug("Inserted ObservedProperty. Created id = {}.", generatedId); + op.setId(entityFactories.idFromObject(generatedId)); + + // Create new datastreams, if any. + for (Datastream ds : op.getDatastreams()) { + ds.setSensor(new Sensor(op.getId())); + ds.complete(); + pm.insert(ds); + } + + // Create new multiDatastreams, if any. + for (MultiDatastream mds : op.getMultiDatastreams()) { + mds.setSensor(new Sensor(op.getId())); + mds.complete(); + pm.insert(mds); + } + + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, ObservedProperty op, J opId) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQObsProperties qop = qCollection.qObsProperties; + SQLUpdateClause update = qFactory.update(qop); + EntityChangedMessage message = new EntityChangedMessage(); + + if (op.isSetDefinition()) { + if (op.getDefinition() == null) { + throw new IncompleteEntityException("definition" + CAN_NOT_BE_NULL); + } + update.set(qop.definition, op.getDefinition()); + message.addField(EntityProperty.DEFINITION); + } + if (op.isSetDescription()) { + if (op.getDescription() == null) { + throw new IncompleteEntityException(EntityProperty.DESCRIPTION.jsonName + CAN_NOT_BE_NULL); + } + update.set(qop.description, op.getDescription()); + message.addField(EntityProperty.DESCRIPTION); + } + if (op.isSetName()) { + if (op.getName() == null) { + throw new IncompleteEntityException("name" + CAN_NOT_BE_NULL); + } + update.set(qop.name, op.getName()); + message.addField(EntityProperty.NAME); + } + if (op.isSetProperties()) { + update.set(qop.properties, EntityFactories.objectToJson(op.getProperties())); + message.addField(EntityProperty.PROPERTIES); + } + + update.where(qop.getId().eq(opId)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating ObservedProperty {} caused {} rows to change!", opId, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + + linkDatastreams(op, pm, qFactory, opId); + + if (!op.getMultiDatastreams().isEmpty()) { + throw new IllegalArgumentException("Can not add MultiDatastreams to an ObservedProperty."); + } + + LOGGER.debug("Updated ObservedProperty {}", opId); + return message; + } + + private void linkDatastreams(ObservedProperty op, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J opId) throws NoSuchEntityException { + // Link existing Datastreams to the observedProperty. + for (Datastream ds : op.getDatastreams()) { + if (ds.getId() == null || !entityFactories.entityExists(pm, ds)) { + throw new NoSuchEntityException("ObservedProperty" + NO_ID_OR_NOT_FOUND); + } + J dsId = (J) ds.getId().getValue(); + AbstractQDatastreams qds = qCollection.qDatastreams; + long dsCount = qFactory.update(qds) + .set(qds.getObsPropertyId(), opId) + .where(qds.getId().eq(dsId)) + .execute(); + if (dsCount > 0) { + LOGGER.debug("Assigned datastream {} to ObservedProperty {}.", dsId, opId); + } + } + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + // First delete all MultiDatastreams that link to this ObservedProperty. + // Must happen first, since the links in the link table would be gone otherwise. + AbstractQMultiDatastreams qMd = qCollection.qMultiDatastreams; + AbstractQMultiDatastreamsObsProperties qMdOp = qCollection.qMultiDatastreamsObsProperties; + long count = pm.createQueryFactory() + .delete(qMd) + .where(qMd.getId() + .in( + SQLExpressions.select(qMdOp.getMultiDatastreamId()).from(qMdOp).where(qMdOp.getObsPropertyId().eq(entityId)) + )) + .execute(); + LOGGER.debug("Deleted {} MultiDatastreams.", count); + // Then actually delete the OP. + count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("ObservedProperty " + entityId + " not found."); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.OBSERVEDPROPERTY; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/SensorFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/SensorFactory.java new file mode 100644 index 000000000..1d39f081f --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/SensorFactory.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; +import de.fraunhofer.iosb.ilt.sta.model.Sensor; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.NO_ID_OR_NOT_FOUND; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQSensors; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class SensorFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(SensorFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQSensors qInstance; + private final QCollection qCollection; + + public SensorFactory(EntityFactories factories, AbstractQSensors qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public Sensor create(Tuple tuple, Query query, DataSize dataSize) { + Set select = query == null ? Collections.emptySet() : query.getSelect(); + Sensor entity = new Sensor(); + entity.setName(tuple.get(qInstance.name)); + entity.setDescription(tuple.get(qInstance.description)); + entity.setEncodingType(tuple.get(qInstance.encodingType)); + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + if (select.isEmpty() || select.contains(EntityProperty.METADATA)) { + String metaDataString = tuple.get(qInstance.metadata); + dataSize.increase(metaDataString == null ? 0 : metaDataString.length()); + entity.setMetadata(metaDataString); + } + if (select.isEmpty() || select.contains(EntityProperty.PROPERTIES)) { + String props = tuple.get(qInstance.properties); + entity.setProperties(Utils.jsonToObject(props, Map.class)); + } + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, Sensor s) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQSensors qs = qCollection.qSensors; + SQLInsertClause insert = qFactory.insert(qs); + insert.set(qs.name, s.getName()); + insert.set(qs.description, s.getDescription()); + insert.set(qs.encodingType, s.getEncodingType()); + // TODO: Check metadata serialisation. + insert.set(qs.metadata, s.getMetadata().toString()); + insert.set(qs.properties, EntityFactories.objectToJson(s.getProperties())); + + entityFactories.insertUserDefinedId(pm, insert, qs.getId(), s); + + J generatedId = insert.executeWithKey(qs.getId()); + LOGGER.debug("Inserted Sensor. Created id = {}.", generatedId); + s.setId(entityFactories.idFromObject(generatedId)); + + // Create new datastreams, if any. + for (Datastream ds : s.getDatastreams()) { + ds.setSensor(new Sensor(s.getId())); + ds.complete(); + pm.insert(ds); + } + + // Create new multiDatastreams, if any. + for (MultiDatastream mds : s.getMultiDatastreams()) { + mds.setSensor(new Sensor(s.getId())); + mds.complete(); + pm.insert(mds); + } + + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, Sensor s, J sensorId) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQSensors qs = qCollection.qSensors; + SQLUpdateClause update = qFactory.update(qs); + EntityChangedMessage message = new EntityChangedMessage(); + + if (s.isSetName()) { + if (s.getName() == null) { + throw new IncompleteEntityException("name" + CAN_NOT_BE_NULL); + } + update.set(qs.name, s.getName()); + message.addField(EntityProperty.NAME); + } + if (s.isSetDescription()) { + if (s.getDescription() == null) { + throw new IncompleteEntityException(EntityProperty.DESCRIPTION.jsonName + CAN_NOT_BE_NULL); + } + update.set(qs.description, s.getDescription()); + message.addField(EntityProperty.DESCRIPTION); + } + if (s.isSetEncodingType()) { + if (s.getEncodingType() == null) { + throw new IncompleteEntityException("encodingType" + CAN_NOT_BE_NULL); + } + update.set(qs.encodingType, s.getEncodingType()); + message.addField(EntityProperty.ENCODINGTYPE); + } + if (s.isSetMetadata()) { + if (s.getMetadata() == null) { + throw new IncompleteEntityException("metadata" + CAN_NOT_BE_NULL); + } + // TODO: Check metadata serialisation. + update.set(qs.metadata, s.getMetadata().toString()); + message.addField(EntityProperty.METADATA); + } + if (s.isSetProperties()) { + update.set(qs.properties, EntityFactories.objectToJson(s.getProperties())); + message.addField(EntityProperty.PROPERTIES); + } + + update.where(qs.getId().eq(sensorId)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating Sensor {} caused {} rows to change!", sensorId, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + + linkExistingDatastreams(s, pm, qFactory, sensorId); + + linkExistingMultiDatastreams(s, pm, qFactory, sensorId); + + LOGGER.debug("Updated Sensor {}", sensorId); + return message; + } + + private void linkExistingMultiDatastreams(Sensor s, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J sensorId) throws NoSuchEntityException { + for (MultiDatastream mds : s.getMultiDatastreams()) { + if (mds.getId() == null || !entityFactories.entityExists(pm, mds)) { + throw new NoSuchEntityException("MultiDatastream" + NO_ID_OR_NOT_FOUND); + } + J mdsId = (J) mds.getId().getValue(); + AbstractQMultiDatastreams qmds = qCollection.qMultiDatastreams; + long mdsCount = qFactory.update(qmds) + .set(qmds.getSensorId(), sensorId) + .where(qmds.getId().eq(mdsId)) + .execute(); + if (mdsCount > 0) { + LOGGER.debug("Assigned multiDatastream {} to sensor {}.", mdsId, sensorId); + } + } + } + + private void linkExistingDatastreams(Sensor s, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J sensorId) throws NoSuchEntityException { + for (Datastream ds : s.getDatastreams()) { + if (ds.getId() == null || !entityFactories.entityExists(pm, ds)) { + throw new NoSuchEntityException("Datastream" + NO_ID_OR_NOT_FOUND); + } + J dsId = (J) ds.getId().getValue(); + AbstractQDatastreams qds = qCollection.qDatastreams; + long dsCount = qFactory.update(qds) + .set(qds.getSensorId(), sensorId) + .where(qds.getId().eq(dsId)) + .execute(); + if (dsCount > 0) { + LOGGER.debug("Assigned datastream {} to sensor {}.", dsId, sensorId); + } + } + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("Sensor " + entityId + " not found."); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.SENSOR; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ThingFactory.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ThingFactory.java new file mode 100644 index 000000000..9327a5580 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/factories/ThingFactory.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.factories; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.SQLQueryFactory; +import com.querydsl.sql.dml.SQLInsertClause; +import com.querydsl.sql.dml.SQLUpdateClause; +import de.fraunhofer.iosb.ilt.sta.messagebus.EntityChangedMessage; +import de.fraunhofer.iosb.ilt.sta.model.Datastream; +import de.fraunhofer.iosb.ilt.sta.model.Location; +import de.fraunhofer.iosb.ilt.sta.model.MultiDatastream; +import de.fraunhofer.iosb.ilt.sta.model.Thing; +import de.fraunhofer.iosb.ilt.sta.path.EntityProperty; +import de.fraunhofer.iosb.ilt.sta.path.EntityType; +import de.fraunhofer.iosb.ilt.sta.path.Property; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.DataSize; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CAN_NOT_BE_NULL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CHANGED_MULTIPLE_ROWS; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.CREATED_HL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.LINKED_L_TO_HL; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.LINKED_L_TO_T; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.NO_ID_OR_NOT_FOUND; +import static de.fraunhofer.iosb.ilt.sta.persistence.postgres.EntityFactories.UNLINKED_L_FROM_T; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.PostgresPersistenceManager; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.Utils; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQLocationsHistLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQMultiDatastreams; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThings; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.AbstractQThingsLocations; +import de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths.QCollection; +import de.fraunhofer.iosb.ilt.sta.query.Query; +import de.fraunhofer.iosb.ilt.sta.util.IncompleteEntityException; +import de.fraunhofer.iosb.ilt.sta.util.NoSuchEntityException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class ThingFactory & Path, J> implements EntityFactory { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(ThingFactory.class); + + private final EntityFactories entityFactories; + private final AbstractQThings qInstance; + private final QCollection qCollection; + + public ThingFactory(EntityFactories factories, AbstractQThings qInstance) { + this.entityFactories = factories; + this.qInstance = qInstance; + this.qCollection = factories.qCollection; + } + + @Override + public Thing create(Tuple tuple, Query query, DataSize dataSize) { + Set select = query == null ? Collections.emptySet() : query.getSelect(); + Thing entity = new Thing(); + entity.setName(tuple.get(qInstance.name)); + entity.setDescription(tuple.get(qInstance.description)); + J id = entityFactories.getIdFromTuple(tuple, qInstance.getId()); + if (id != null) { + entity.setId(entityFactories.idFromObject(id)); + } + if (select.isEmpty() || select.contains(EntityProperty.PROPERTIES)) { + String props = tuple.get(qInstance.properties); + dataSize.increase(props == null ? 0 : props.length()); + entity.setProperties(Utils.jsonToObject(props, Map.class)); + } + return entity; + } + + @Override + public boolean insert(PostgresPersistenceManager pm, Thing t) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQThings qt = qCollection.qThings; + SQLInsertClause insert = qFactory.insert(qt); + insert.set(qt.name, t.getName()); + insert.set(qt.description, t.getDescription()); + insert.set(qt.properties, EntityFactories.objectToJson(t.getProperties())); + + entityFactories.insertUserDefinedId(pm, insert, qt.getId(), t); + + J thingId = insert.executeWithKey(qt.getId()); + LOGGER.debug("Inserted Thing. Created id = {}.", thingId); + t.setId(entityFactories.idFromObject(thingId)); + + // Create new Locations, if any. + List locationIds = new ArrayList<>(); + for (Location l : t.getLocations()) { + entityFactories.entityExistsOrCreate(pm, l); + J lId = (J) l.getId().getValue(); + + AbstractQThingsLocations qtl = qCollection.qThingsLocations; + insert = qFactory.insert(qtl); + insert.set(qtl.getThingId(), thingId); + insert.set(qtl.getLocationId(), lId); + insert.execute(); + LOGGER.debug(LINKED_L_TO_T, lId, thingId); + locationIds.add(lId); + } + + // Now link the new locations also to a historicalLocation. + if (!locationIds.isEmpty()) { + AbstractQHistLocations qhl = qCollection.qHistLocations; + insert = qFactory.insert(qhl); + insert.set(qhl.getThingId(), thingId); + insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); + // TODO: maybe use histLocationId based on locationIds + J histLocationId = insert.executeWithKey(qhl.getId()); + LOGGER.debug(CREATED_HL, histLocationId); + + AbstractQLocationsHistLocations qlhl = qCollection.qLocationsHistLocations; + for (J locId : locationIds) { + qFactory.insert(qlhl) + .set(qlhl.getHistLocationId(), histLocationId) + .set(qlhl.getLocationId(), locId) + .execute(); + LOGGER.debug(LINKED_L_TO_HL, locId, histLocationId); + } + } + + // Create new datastreams, if any. + for (Datastream ds : t.getDatastreams()) { + ds.setThing(new Thing(t.getId())); + ds.complete(); + pm.insert(ds); + } + + // Create new multiDatastreams, if any. + for (MultiDatastream mds : t.getMultiDatastreams()) { + mds.setThing(new Thing(t.getId())); + mds.complete(); + pm.insert(mds); + } + + // TODO: if we allow the creation of historicalLocations through Things + // then we have to be able to link those to Locations we might have just created. + // However, id juggling will be needed! + return true; + } + + @Override + public EntityChangedMessage update(PostgresPersistenceManager pm, Thing t, J thingId) throws NoSuchEntityException, IncompleteEntityException { + SQLQueryFactory qFactory = pm.createQueryFactory(); + AbstractQThings qt = qCollection.qThings; + SQLUpdateClause update = qFactory.update(qt); + EntityChangedMessage message = new EntityChangedMessage(); + + if (t.isSetName()) { + if (t.getName() == null) { + throw new IncompleteEntityException("name" + CAN_NOT_BE_NULL); + } + update.set(qt.name, t.getName()); + message.addField(EntityProperty.NAME); + } + if (t.isSetDescription()) { + if (t.getDescription() == null) { + throw new IncompleteEntityException(EntityProperty.DESCRIPTION.jsonName + CAN_NOT_BE_NULL); + } + update.set(qt.description, t.getDescription()); + message.addField(EntityProperty.DESCRIPTION); + } + if (t.isSetProperties()) { + update.set(qt.properties, EntityFactories.objectToJson(t.getProperties())); + message.addField(EntityProperty.PROPERTIES); + } + update.where(qt.getId().eq(thingId)); + long count = 0; + if (!update.isEmpty()) { + count = update.execute(); + } + if (count > 1) { + LOGGER.error("Updating Thing {} caused {} rows to change!", thingId, count); + throw new IllegalStateException(CHANGED_MULTIPLE_ROWS); + } + LOGGER.debug("Updated Thing {}", thingId); + + linkExistingDatastreams(t, pm, qFactory, thingId); + + linkExistingMultiDatastreams(t, pm, qFactory, thingId); + + linkExistingLocations(t, qFactory, thingId, pm); + return message; + } + + private void linkExistingLocations(Thing t, SQLQueryFactory qFactory, J thingId, PostgresPersistenceManager pm) throws NoSuchEntityException { + if (t.getLocations().isEmpty()) { + return; + } + // Unlink old Locations from Thing. + AbstractQThingsLocations qtl = qCollection.qThingsLocations; + long count = qFactory.delete(qtl).where(qtl.getThingId().eq(thingId)).execute(); + LOGGER.debug(UNLINKED_L_FROM_T, count, thingId); + + // Link new locations to Thing, track the ids. + List locationIds = new ArrayList<>(); + for (Location l : t.getLocations()) { + if (l.getId() == null || !entityFactories.entityExists(pm, l)) { + throw new NoSuchEntityException("Location with no id."); + } + J locationId = (J) l.getId().getValue(); + + SQLInsertClause insert = qFactory.insert(qtl); + insert.set(qtl.getThingId(), thingId); + insert.set(qtl.getLocationId(), locationId); + insert.execute(); + LOGGER.debug(LINKED_L_TO_T, locationId, thingId); + locationIds.add(locationId); + } + + // Now link the newly linked locations also to a historicalLocation. + if (!locationIds.isEmpty()) { + AbstractQHistLocations qhl = qCollection.qHistLocations; + SQLInsertClause insert = qFactory.insert(qhl); + insert.set(qhl.getThingId(), thingId); + insert.set(qhl.time, new Timestamp(Calendar.getInstance().getTimeInMillis())); + // TODO: maybe use histLocationId based on locationIds + J histLocationId = insert.executeWithKey(qhl.getId()); + LOGGER.debug(CREATED_HL, histLocationId); + + AbstractQLocationsHistLocations qlhl = qCollection.qLocationsHistLocations; + for (J locId : locationIds) { + qFactory.insert(qlhl) + .set(qlhl.getHistLocationId(), histLocationId) + .set(qlhl.getLocationId(), locId) + .execute(); + LOGGER.debug(LINKED_L_TO_HL, locId, histLocationId); + } + } + } + + private void linkExistingMultiDatastreams(Thing t, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J thingId) throws NoSuchEntityException { + // Link existing MultiDatastreams to the thing. + for (MultiDatastream mds : t.getMultiDatastreams()) { + if (mds.getId() == null || !entityFactories.entityExists(pm, mds)) { + throw new NoSuchEntityException("MultiDatastream" + NO_ID_OR_NOT_FOUND); + } + J mdsId = (J) mds.getId().getValue(); + AbstractQMultiDatastreams qmds = qCollection.qMultiDatastreams; + long mdsCount = qFactory.update(qmds) + .set(qmds.getThingId(), thingId) + .where(qmds.getId().eq(mdsId)) + .execute(); + if (mdsCount > 0) { + LOGGER.debug("Assigned multiDatastream {} to thing {}.", mdsId, thingId); + } + } + } + + private void linkExistingDatastreams(Thing t, PostgresPersistenceManager pm, SQLQueryFactory qFactory, J thingId) throws NoSuchEntityException { + // Link existing Datastreams to the thing. + for (Datastream ds : t.getDatastreams()) { + if (ds.getId() == null || !entityFactories.entityExists(pm, ds)) { + throw new NoSuchEntityException("Datastream" + NO_ID_OR_NOT_FOUND); + } + J dsId = (J) ds.getId().getValue(); + AbstractQDatastreams qds = qCollection.qDatastreams; + long dsCount = qFactory.update(qds) + .set(qds.getThingId(), thingId) + .where(qds.getId().eq(dsId)) + .execute(); + if (dsCount > 0) { + LOGGER.debug("Assigned datastream {} to thing {}.", dsId, thingId); + } + } + } + + @Override + public void delete(PostgresPersistenceManager pm, J entityId) throws NoSuchEntityException { + long count = pm.createQueryFactory() + .delete(qInstance) + .where(qInstance.getId().eq(entityId)) + .execute(); + if (count == 0) { + throw new NoSuchEntityException("Thing " + entityId + " not found."); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.THING; + } + + @Override + public I getPrimaryKey() { + return qInstance.getId(); + } + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQDatastreams.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQDatastreams.java new file mode 100644 index 000000000..59b79c5f0 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQDatastreams.java @@ -0,0 +1,91 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.spatial.GeometryPath; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQDatastreams is a Querydsl query type for AbstractQDatastreams + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQDatastreams & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = -222215350; + + public final StringPath description = createString("description"); + + public final StringPath name = createString("name"); + + public final StringPath observationType = createString("observationType"); + + public final GeometryPath observedArea = createGeometry("observedArea", org.geolatte.geom.Geometry.class); + + public final DateTimePath phenomenonTimeEnd = createDateTime("phenomenonTimeEnd", java.sql.Timestamp.class); + + public final DateTimePath phenomenonTimeStart = createDateTime("phenomenonTimeStart", java.sql.Timestamp.class); + + public final StringPath properties = createString("properties"); + + public final DateTimePath resultTimeEnd = createDateTime("resultTimeEnd", java.sql.Timestamp.class); + + public final DateTimePath resultTimeStart = createDateTime("resultTimeStart", java.sql.Timestamp.class); + + public final StringPath unitDefinition = createString("unitDefinition"); + + public final StringPath unitName = createString("unitName"); + + public final StringPath unitSymbol = createString("unitSymbol"); + + public AbstractQDatastreams(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(description, ColumnMetadata.named("DESCRIPTION").ofType(Types.CLOB).withSize(2147483647)); + addMetadata(name, ColumnMetadata.named("NAME").ofType(Types.CLOB).withSize(2147483647)); + addMetadata(observationType, ColumnMetadata.named("OBSERVATION_TYPE").ofType(Types.CLOB).withSize(2147483647)); + addMetadata(observedArea, ColumnMetadata.named("OBSERVED_AREA").ofType(Types.OTHER).withSize(2147483647)); + addMetadata(phenomenonTimeEnd, ColumnMetadata.named("PHENOMENON_TIME_END").ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(phenomenonTimeStart, ColumnMetadata.named("PHENOMENON_TIME_START").ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(properties, ColumnMetadata.named("PROPERTIES").ofType(Types.CLOB).withSize(2147483647)); + addMetadata(resultTimeEnd, ColumnMetadata.named("RESULT_TIME_END").ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(resultTimeStart, ColumnMetadata.named("RESULT_TIME_START").ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(unitDefinition, ColumnMetadata.named("UNIT_DEFINITION").ofType(Types.VARCHAR).withSize(255)); + addMetadata(unitName, ColumnMetadata.named("UNIT_NAME").ofType(Types.VARCHAR).withSize(255)); + addMetadata(unitSymbol, ColumnMetadata.named("UNIT_SYMBOL").ofType(Types.VARCHAR).withSize(255)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * @return the Path to the observed property id + */ + public abstract I getObsPropertyId(); + + /** + * @return the path to the sensor id + */ + public abstract I getSensorId(); + + /** + * @return the path to the thing id + */ + public abstract I getThingId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQDatastreams newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQFeatures.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQFeatures.java new file mode 100644 index 000000000..8d9f352ef --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQFeatures.java @@ -0,0 +1,58 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.spatial.GeometryPath; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQFeatures is a Querydsl query type for AbstractQFeatures + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQFeatures & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = 906833564; + + public final StringPath description = createString("description"); + + public final StringPath encodingType = createString("encodingType"); + + public final StringPath feature = createString("feature"); + + public final GeometryPath geom = createGeometry("geom", org.geolatte.geom.Geometry.class); + + public final StringPath name = createString("name"); + + public final StringPath properties = createString("properties"); + + public AbstractQFeatures(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(description, ColumnMetadata.named("DESCRIPTION").withIndex(2).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(encodingType, ColumnMetadata.named("ENCODING_TYPE").withIndex(3).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(feature, ColumnMetadata.named("FEATURE").withIndex(4).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(geom, ColumnMetadata.named("GEOM").withIndex(5).ofType(Types.OTHER).withSize(2147483647)); + addMetadata(name, ColumnMetadata.named("NAME").withIndex(6).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(properties, ColumnMetadata.named("PROPERTIES").withIndex(7).ofType(Types.CLOB).withSize(2147483647)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQFeatures newWithAlias(String variable); + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQHistLocations.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQHistLocations.java new file mode 100644 index 000000000..733699548 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQHistLocations.java @@ -0,0 +1,47 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQHistLocations is a Querydsl query type for AbstractQHistLocations + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQHistLocations & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = 244045661; + + public final DateTimePath time = createDateTime("time", java.sql.Timestamp.class); + + public AbstractQHistLocations(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(time, ColumnMetadata.named("TIME").withIndex(2).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * @return The Path to the Thing id. + */ + public abstract I getThingId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQHistLocations newWithAlias(String variable); + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQLocations.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQLocations.java new file mode 100644 index 000000000..a3a5efffa --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQLocations.java @@ -0,0 +1,62 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.spatial.GeometryPath; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQLocations is a Querydsl query type for AbstractQLocations + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQLocations & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = 1565350111; + + public final StringPath description = createString("description"); + + public final StringPath encodingType = createString("encodingType"); + + public final GeometryPath geom = createGeometry("geom", org.geolatte.geom.Geometry.class); + + public final StringPath location = createString("location"); + + public final StringPath name = createString("name"); + + public final StringPath properties = createString("properties"); + + public AbstractQLocations(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(description, ColumnMetadata.named("DESCRIPTION").withIndex(2).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(encodingType, ColumnMetadata.named("ENCODING_TYPE").withIndex(3).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(geom, ColumnMetadata.named("GEOM").withIndex(5).ofType(Types.OTHER).withSize(2147483647)); + addMetadata(location, ColumnMetadata.named("LOCATION").withIndex(4).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(name, ColumnMetadata.named("NAME").withIndex(6).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(properties, ColumnMetadata.named("PROPERTIES").withIndex(8).ofType(Types.CLOB).withSize(2147483647)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * @return The Path to the generated FoI id. + */ + public abstract I getGenFoiId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQLocations newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQLocationsHistLocations.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQLocationsHistLocations.java new file mode 100644 index 000000000..7d78a3d00 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQLocationsHistLocations.java @@ -0,0 +1,42 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.spatial.RelationalPathSpatial; + +/** + * AbstractQLocationsHistLocations is a Querydsl query type for + * AbstractQLocationsHistLocations + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQLocationsHistLocations & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = 1713698749; + + public AbstractQLocationsHistLocations(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + } + + /** + * @return The Path to the Location id. + */ + public abstract I getLocationId(); + + /** + * @return The Path to the HistLocation id. + */ + public abstract I getHistLocationId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQLocationsHistLocations newWithAlias(String variable); + +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQMultiDatastreams.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQMultiDatastreams.java new file mode 100644 index 000000000..d6d33e9cb --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQMultiDatastreams.java @@ -0,0 +1,79 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.*; +import com.querydsl.spatial.*; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQMultiDatastreams is a Querydsl query type for + * AbstractQMultiDatastreams + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQMultiDatastreams & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = -1916297617; + + public final StringPath description = createString("description"); + + public final StringPath name = createString("name"); + + public final StringPath observationTypes = createString("observationTypes"); + + public final GeometryPath observedArea = createGeometry("observedArea", org.geolatte.geom.Geometry.class); + + public final DateTimePath phenomenonTimeEnd = createDateTime("phenomenonTimeEnd", java.sql.Timestamp.class); + + public final DateTimePath phenomenonTimeStart = createDateTime("phenomenonTimeStart", java.sql.Timestamp.class); + + public final StringPath properties = createString("properties"); + + public final DateTimePath resultTimeEnd = createDateTime("resultTimeEnd", java.sql.Timestamp.class); + + public final DateTimePath resultTimeStart = createDateTime("resultTimeStart", java.sql.Timestamp.class); + + public final StringPath unitOfMeasurements = createString("unitOfMeasurements"); + + public AbstractQMultiDatastreams(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(description, ColumnMetadata.named("DESCRIPTION").withIndex(3).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(name, ColumnMetadata.named("NAME").withIndex(2).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(observationTypes, ColumnMetadata.named("OBSERVATION_TYPES").withIndex(4).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(observedArea, ColumnMetadata.named("OBSERVED_AREA").withIndex(12).ofType(Types.OTHER).withSize(2147483647)); + addMetadata(phenomenonTimeEnd, ColumnMetadata.named("PHENOMENON_TIME_END").withIndex(6).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(phenomenonTimeStart, ColumnMetadata.named("PHENOMENON_TIME_START").withIndex(5).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(properties, ColumnMetadata.named("PROPERTIES").withIndex(13).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(resultTimeEnd, ColumnMetadata.named("RESULT_TIME_END").withIndex(8).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(resultTimeStart, ColumnMetadata.named("RESULT_TIME_START").withIndex(7).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(unitOfMeasurements, ColumnMetadata.named("UNIT_OF_MEASUREMENTS").withIndex(11).ofType(Types.CLOB).withSize(2147483647)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * @return the path to the sensor id + */ + public abstract I getSensorId(); + + /** + * @return the path to the thing id + */ + public abstract I getThingId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQMultiDatastreams newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQMultiDatastreamsObsProperties.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQMultiDatastreamsObsProperties.java new file mode 100644 index 000000000..53720435a --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQMultiDatastreamsObsProperties.java @@ -0,0 +1,47 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQMultiDatastreamsObsProperties is a Querydsl query type for + * AbstractQMultiDatastreamsObsProperties + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQMultiDatastreamsObsProperties & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = -838888412; + + public final NumberPath rank = createNumber("rank", Integer.class); + + public AbstractQMultiDatastreamsObsProperties(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(rank, ColumnMetadata.named("RANK").withIndex(3).ofType(Types.INTEGER).withSize(10).notNull()); + } + + /** + * @return The Path to the id of the MultiDatastream. + */ + public abstract I getMultiDatastreamId(); + + /** + * @return The Path to the id of the ObservedProperties. + */ + public abstract I getObsPropertyId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQMultiDatastreamsObsProperties newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQObsProperties.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQObsProperties.java new file mode 100644 index 000000000..de5f4104a --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQObsProperties.java @@ -0,0 +1,50 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQObsProperties is a Querydsl query type for AbstractQObsProperties + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQObsProperties & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = -1131991212; + + public final StringPath definition = createString("definition"); + + public final StringPath description = createString("description"); + + public final StringPath name = createString("name"); + + public final StringPath properties = createString("properties"); + + public AbstractQObsProperties(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(definition, ColumnMetadata.named("DEFINITION").withIndex(3).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(description, ColumnMetadata.named("DESCRIPTION").withIndex(4).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(name, ColumnMetadata.named("NAME").withIndex(2).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(properties, ColumnMetadata.named("PROPERTIES").withIndex(5).ofType(Types.CLOB).withSize(2147483647)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQObsProperties newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQObservations.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQObservations.java new file mode 100644 index 000000000..cf7951775 --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQObservations.java @@ -0,0 +1,92 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.BooleanPath; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQObservations is a Querydsl query type for AbstractQObservations + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQObservations & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = -1854525274; + + public final StringPath parameters = createString("parameters"); + + public final DateTimePath phenomenonTimeEnd = createDateTime("phenomenonTimeEnd", java.sql.Timestamp.class); + + public final DateTimePath phenomenonTimeStart = createDateTime("phenomenonTimeStart", java.sql.Timestamp.class); + + public final BooleanPath resultBoolean = createBoolean("resultBoolean"); + + public final StringPath resultJson = createString("resultJson"); + + public final NumberPath resultNumber = createNumber("resultNumber", Double.class); + + public final StringPath resultQuality = createString("resultQuality"); + + public final StringPath resultString = createString("resultString"); + + public final DateTimePath resultTime = createDateTime("resultTime", java.sql.Timestamp.class); + + public final NumberPath resultType = createNumber("resultType", Byte.class); + + public final DateTimePath validTimeEnd = createDateTime("validTimeEnd", java.sql.Timestamp.class); + + public final DateTimePath validTimeStart = createDateTime("validTimeStart", java.sql.Timestamp.class); + + public AbstractQObservations(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(parameters, ColumnMetadata.named("PARAMETERS").withIndex(10).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(phenomenonTimeEnd, ColumnMetadata.named("PHENOMENON_TIME_END").withIndex(3).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(phenomenonTimeStart, ColumnMetadata.named("PHENOMENON_TIME_START").withIndex(2).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(resultBoolean, ColumnMetadata.named("RESULT_BOOLEAN").withIndex(15).ofType(Types.BOOLEAN).withSize(1)); + addMetadata(resultJson, ColumnMetadata.named("RESULT_JSON").withIndex(14).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(resultNumber, ColumnMetadata.named("RESULT_NUMBER").withIndex(5).ofType(Types.DOUBLE).withSize(17)); + addMetadata(resultQuality, ColumnMetadata.named("RESULT_QUALITY").withIndex(7).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(resultString, ColumnMetadata.named("RESULT_STRING").withIndex(6).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(resultTime, ColumnMetadata.named("RESULT_TIME").withIndex(4).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(resultType, ColumnMetadata.named("RESULT_TYPE").withIndex(13).ofType(Types.TINYINT).withSize(3)); + addMetadata(validTimeEnd, ColumnMetadata.named("VALID_TIME_END").withIndex(9).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + addMetadata(validTimeStart, ColumnMetadata.named("VALID_TIME_START").withIndex(8).ofType(Types.TIMESTAMP).withSize(23).withDigits(10)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * @return The Path to the id of the Datastream. + */ + public abstract I getDatastreamId(); + + /** + * @return The Path to the id of the MultiDatastream. + */ + public abstract I getMultiDatastreamId(); + + /** + * @return The Path to the id of the FeatureOfInterest. + */ + public abstract I getFeatureId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQObservations newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQSensors.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQSensors.java new file mode 100644 index 000000000..dee8f7fff --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQSensors.java @@ -0,0 +1,53 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQSensors is a Querydsl query type for AbstractQSensors + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQSensors & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = 2019004858; + + public final StringPath description = createString("description"); + + public final StringPath encodingType = createString("encodingType"); + + public final StringPath metadata = createString("metadata"); + + public final StringPath name = createString("name"); + + public final StringPath properties = createString("properties"); + + public AbstractQSensors(Class type, PathMetadata pathMetadata, String schema, String table) { + super(type, pathMetadata, schema, table); + addMetadata(description, ColumnMetadata.named("DESCRIPTION").withIndex(2).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(encodingType, ColumnMetadata.named("ENCODING_TYPE").withIndex(3).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(metadata, ColumnMetadata.named("METADATA").withIndex(4).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(name, ColumnMetadata.named("NAME").withIndex(5).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(properties, ColumnMetadata.named("PROPERTIES").withIndex(6).ofType(Types.CLOB).withSize(2147483647)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQSensors newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQThings.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQThings.java new file mode 100644 index 000000000..e641ecfbd --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQThings.java @@ -0,0 +1,47 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.sql.ColumnMetadata; +import com.querydsl.sql.spatial.RelationalPathSpatial; +import java.sql.Types; + +/** + * AbstractQThings is a Querydsl query type for AbstractQThings + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQThings & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = -180719772; + + public final StringPath description = createString("description"); + + public final StringPath name = createString("name"); + + public final StringPath properties = createString("properties"); + + public AbstractQThings(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + addMetadata(description, ColumnMetadata.named("DESCRIPTION").withIndex(2).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(name, ColumnMetadata.named("NAME").withIndex(4).ofType(Types.CLOB).withSize(2147483647)); + addMetadata(properties, ColumnMetadata.named("PROPERTIES").withIndex(3).ofType(Types.CLOB).withSize(2147483647)); + } + + /** + * @return The Path to the id. + */ + public abstract I getId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQThings newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQThingsLocations.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQThingsLocations.java new file mode 100644 index 000000000..c55eb548a --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/AbstractQThingsLocations.java @@ -0,0 +1,41 @@ +package de.fraunhofer.iosb.ilt.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.sql.spatial.RelationalPathSpatial; + +/** + * AbstractQThingsLocations is a Querydsl query type for + * AbstractQThingsLocations + * + * @param The implementation of this abstract class. + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public abstract class AbstractQThingsLocations & Path, J> extends RelationalPathSpatial { + + private static final long serialVersionUID = -1059514278; + + public AbstractQThingsLocations(Class type, PathMetadata metadata, String schema, String table) { + super(type, metadata, schema, table); + } + + /** + * @return The Path to the id of the Thing. + */ + public abstract I getThingId(); + + /** + * @return The Path to the id of the Location. + */ + public abstract I getLocationId(); + + /** + * Create a new instance, with a different alias. + * + * @param variable The alias to use for the new instance. + * @return a new instance, with the given alias. + */ + public abstract AbstractQThingsLocations newWithAlias(String variable); +} diff --git a/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/QCollection.java b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/QCollection.java new file mode 100644 index 000000000..9041c83ee --- /dev/null +++ b/FROST-Server.SQL/src/main/java/de/fraunhofer/iosb/ilt/sta/persistence/postgres/relationalpaths/QCollection.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 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.sta.persistence.postgres.relationalpaths; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.SimpleExpression; + +/** + * @author scf + * @param The type of path used for the ID fields. + * @param The type of the ID fields. + */ +public class QCollection & Path, J> { + + public final AbstractQDatastreams qDatastreams; + public final AbstractQMultiDatastreams qMultiDatastreams; + public final AbstractQThings qThings; + public final AbstractQFeatures qFeatures; + public final AbstractQHistLocations qHistLocations; + public final AbstractQLocations qLocations; + public final AbstractQSensors qSensors; + public final AbstractQObservations qObservations; + public final AbstractQObsProperties qObsProperties; + public final AbstractQLocationsHistLocations qLocationsHistLocations; + public final AbstractQMultiDatastreamsObsProperties qMultiDatastreamsObsProperties; + public final AbstractQThingsLocations qThingsLocations; + + public QCollection( + AbstractQDatastreams qDs, + AbstractQFeatures qFeat, + AbstractQHistLocations qHistLoc, + AbstractQLocations qLoc, + AbstractQMultiDatastreams qMds, + AbstractQObsProperties qOps, + AbstractQObservations qObs, + AbstractQSensors qSnsr, + AbstractQThings qThng, + AbstractQLocationsHistLocations qLocHistLoc, + AbstractQMultiDatastreamsObsProperties qMdsObsProps, + AbstractQThingsLocations qThngLoc) { + qDatastreams = qDs; + qMultiDatastreams = qMds; + qThings = qThng; + qFeatures = qFeat; + qHistLocations = qHistLoc; + qLocations = qLoc; + qSensors = qSnsr; + qObservations = qObs; + qObsProperties = qOps; + qLocationsHistLocations = qLocHistLoc; + qMultiDatastreamsObsProperties = qMdsObsProps; + qThingsLocations = qThngLoc; + } + +} diff --git a/README.md b/README.md index 479d69d64..8a4cb8aab 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ is the first complete, open-source implementation of the OGC SensorThings API Pa | Create-Update-Delete | A.3 | Yes | 14 / 14 | | Batch Request | A.4 | Yes | 0 / 0 | | Sensing MultiDatastream Extension | A.5 | Yes | 18 / 18 | -| Sensing Data Array Extension | A.6 | Yes | 2 / 2 | +| Sensing Data Array Extension | A.6 | Yes | 3 / 3 | | MQTT Extension for Create and Update | A.7 | Yes | 4 / 4 | | MQTT Extension for Receiving Updates | A.8 | Yes | 13 / 13 | @@ -24,170 +24,57 @@ We have extended the official test suit with extra tests that can be found [here The official test suit is fully passed. See the wiki page [features](https://github.com/FraunhoferIOSB/FROST-Server/wiki/Features) for more details. -## The very short and crude installation instructions +## Compiling See the [wiki](https://github.com/FraunhoferIOSB/FROST-Server/wiki) for longer installation instructions. -### Compiling +To compile FROST-Server you need to have a JDK and Maven installed. 1. Checkout the project from github: `git clone https://github.com/FraunhoferIOSB/FROST-Server.git` 2. Go to the project root (The top-most directory with a pom.xml) `cd FROST-Server` -3. Give the command `mvn clean install` - This should build the war file in `FROST-Server.HTTP/target/` - - -### Database installation - -1. create PostgreSQL database for the data -2. install the postgis extensions in this database (CREATE EXTENSION postgis;) - -### Database configuration - -The database connection is configured from the [Context](http://tomcat.apache.org/tomcat-8.0-doc/config/context.html) -entry either in server.xml, in `$CATALINA_BASE/conf/[enginename]/[hostname]/appname.xml` -or in `/META-INF/context.xml` inside the war file. If you are running the application -from your IDE, it is easiest to change the context.xml file in the war file. - -1. Copy the [Postgres JDBC jar](http://repo.maven.apache.org/maven2/org/postgresql/postgresql/9.4.1212/postgresql-9.4.1212.jar) -and the [postgis jar](http://repo.maven.apache.org/maven2/net/postgis/postgis-jdbc/2.2.1/postgis-jdbc-2.2.1.jar) -to `$CATALINA_HOME/lib`. -2. Configure the database resource. Either in the Context, or elsewhere in server.xml: - - - - -### Database initialisation or upgrade - -1. Choose which backend to use, by configuring the persistence.persistenceManagerImplementationClass option: - * Default value, using Long values for entity ids, generated in sequence: de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong - * Using String values for entity ids, with new values generated using uuid_generate_v1mc(): de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.PostgresPersistenceManagerString - * Using uuid values for entity ids, with new values generated using uuid_generate_v1mc(): de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.PostgresPersistenceManagerUuid -2. Browse to http://localhost:8080/FROST/DatabaseStatus - -This should initialise/update the database to the latest version and the service -should be ready for use. - - -### Performance and Indices - -By default, only primary and foreign keys have indices on them. If your database grows large -and you notice a significant slowdown, you should check which queries you use most, and -add indices for those queries. A very common one is probably for -Datastreams(x)/observations?$orderby=phenomenonTime desc - -``` -CREATE INDEX "OBS-DS_ID-PHTIME_SE-O_ID" - ON "OBSERVATIONS" - USING btree - ("DATASTREAM_ID", "PHENOMENON_TIME_START" DESC, "PHENOMENON_TIME_END" DESC, "ID"); -``` - - -### Configuration options - -The server is configured from the [Context](http://tomcat.apache.org/tomcat-8.0-doc/config/context.html) -entry either in server.xml, in `$CATALINA_BASE/conf/[enginename]/[hostname]/appname.xml` -or in `/META-INF/context.xml` inside the war file. If you are running the application -from your IDE, it is easiest to change the context.xml file in the war file. It has -the following options: - -* SensorThings API settings - * `serviceRootUrl`: The base URL of the SensorThings Server without version. - * `defaultCount`: The default value for the $count query option. - * `defaultTop`: The default value for the $top query option. - * `maxTop`: The maximum allowed value for the $top query option. - * `useAbsoluteNavigationLinks`: If true, navigationLinks are absolute, otherwise relative. -* MQTT settings - * `mqtt.mqttServerImplementationClass`: The java class used for running the MQTT server (must implement MqttServer interface) - * `mqtt.Enabled`: Specifies wether MQTT support will be enabled or not. - * `mqtt.Port`: The port the MQTT server runs on. - * `mqtt.QoS`: Quality of Service Level for MQTT messages. - * `mqtt.SubscribeMessageQueueSize`: Queue size for messages to be pubslihed via MQTT. - * `mqtt.SubscribeThreadPoolSize`: Number of threads use to dispatch MQTT notifications. - * `mqtt.CreateMessageQueueSize`: Queue size for create observation requests via MQTT . - * `mqtt.CreateThreadPoolSize`: Number of threads use to dispatch observation creation requests. - * `mqtt.Host`: The external IP address or host name the MQTT server should listen on. Set to 0.0.0.0 to listen on all interfaces. - * `mqtt.internalHost`: The internal host name of the MQTT server. - * `mqtt.WebsocketPort`: The port the MQTT server is reachable via WebSocket. -* persistence settings - * `persistence.persistenceManagerImplementationClass`: The java class used for persistence (must implement PersistenceManaher interface) - * `persistence.alwaysOrderbyId`: Always add an 'orderby=id asc' to queries to ensure consistent paging. - * `persistence.db_jndi_datasource`: JNDI data source name +3. Give the command `mvn clean install`. This should build the war and jar files in: + * `FROST-Server.MQTTP/target/` + * `FROST-Server.HTTP/target/` + * `FROST-Server.MQTT/target/` -## Docker support +## Downloading + +Instead of compiling the server yourself, you can also download pre-built war and jar files from: +* [FROST-Server.MQTTP](https://bintray.com/fraunhoferiosb/Maven/download_file?file_path=de%2Ffraunhofer%2Fiosb%2Filt%2FFROST-Server%2FFROST-Server.MQTTP%2F1.8%2FFROST-Server.MQTTP-1.8.war) +* [FROST-Server.HTTP](https://bintray.com/fraunhoferiosb/Maven/download_file?file_path=de%2Ffraunhofer%2Fiosb%2Filt%2FFROST-Server%2FFROST-Server.HTTP%2F1.8%2FFROST-Server.HTTP-1.8.war) +* [FROST-Server.MQTT](https://bintray.com/fraunhoferiosb/Maven/download_file?file_path=de%2Ffraunhofer%2Fiosb%2Filt%2FFROST-Server%2FFROST-Server.MQTT%2F1.8%2FFROST-Server.MQTT-1.8-jar-with-dependencies.jar) + +Or you can use Docker. -There's also the possibility to run FROST-Server and the needed database inside a Docker container. -This dependency is specified inside the ```docker-compose.yaml``` file. +## Package Choice -You can use the prebuild [docker image](https://hub.docker.com/r/fraunhoferiosb/frost-server/) by -running ```docker-compose up```. This will download the latest version of the FROST-Server and starts it -together with the needed database. You can access the server by opening ```http://localhost:8080/FROST/``` in your browser. +There are three packages for FROST-Server. You only need either the MQTTP (all-in-one) package, or the HTTP and MQTT packages. +* For small servers, or for testing purposes the MQTTP package is fine. It contains both the HTTP and MQTT interfaces. +* If you only need the HTTP interface and are not interested in MQTT, you can use just the HTTP package. +* If you need horizontal scalability you want the separate HTTP and MQTT packages. In this case you also need a separate message bus in your cluster, for internal communication. Though if you need scalability you are probably using Docker. See below for Docker and Helm support. -If you want to build your own docker image, you can do this by calling ```mvn dockerfile:build -pl FROST-Server```. +See [docs/architecture-packages.adoc](docs/architecture-packages.adoc) for an overview of the differences. -All data is stored inside the PostGIS database. To keep this state there's a volume automatically mapped to the PostGIS container. -For more information see the ```docker-compose.yaml``` file and the [PostGIS container documentation](https://hub.docker.com/r/mdillon/postgis/) +## Database setup -To have a proper configuration of your server you need to create your own `context.xml` file. -You can use `FROST-Server.HTTP/src/main/webapp/META-INF/context.xml` as template. -Make sure your adapted file is located in `$CATALINA_HOME/conf/Catalina/localhost/FROST-Server.xml`. +See [docs/postgresql.adoc](docs/postgresql.adoc) for an overview on how to configure PostgreSQL and Tomcat/Wildfly. + +## Configuration options + +See [docs/settings.adoc](docs/settings.adoc) for an overview of all the the configuration settings. + +## Docker support + +See [docs/docker.adoc](docs/docker.adoc) for how to use the FROST-Server docker images. + +## Kubernetes (Helm) support + +See [helm/frost-server/README.md](helm/frost-server/README.md) for how to use the FROST-Server Helm chart for a Kubernetes deployment. ## Standalone Spring Boot If you prefer to not use Tomcat, [Kinota Server](https://github.com/kinota/kinota-server) is a - Spring Boot application that makes it easy to run Fraunhofer IOSB FROST-Server in cloud environments. - - -## Wildfly 10 - -If you prefer not to use Tomcat but [Wildfly](https://github.com/wildfly/wildfly) it is possible to run FROST-Server on it. - -Create a directory $WILDFLY_HOME/modules/org/postgresql/main and add both [Postgres JDBC jar](http://repo.maven.apache.org/maven2/org/postgresql/postgresql/9.4.1212/postgresql-9.4.1212.jar) -and the [postgis jar](http://repo.maven.apache.org/maven2/net/postgis/postgis-jdbc/2.2.1/postgis-jdbc-2.2.1.jar) to it. Create a file named module.xml and add the following: - -```xml - - - - - - - - - - - -``` - -Add a datasource to $WILDFLY_HOME/standalone/configuration/[standalone.xml] -Example: - -```xml - - - - jdbc:postgresql://localhost:5432/sensorthings - org.postgresql.Driver - postgres - - sensorthings - ChangeMe - - - - true - - - - - -``` - +Spring Boot application that makes it easy to run Fraunhofer IOSB FROST-Server in cloud environments. # Authors diff --git a/docker-compose-separated.yaml b/docker-compose-separated.yaml new file mode 100644 index 000000000..6c874ad7e --- /dev/null +++ b/docker-compose-separated.yaml @@ -0,0 +1,57 @@ +version: '2' + +services: + web: + image: fraunhoferiosb/frost-server-http:latest + ports: + - 8080:8080 + depends_on: + - database + - mosquitto + environment: + - serviceRootUrl=http://localhost:8080/FROST-Server + - http_cors_enable=true + - http_cors_allowed.origins=* + - bus_mqttBroker=tcp://mosquitto:1883 + - persistence_db_driver=org.postgresql.Driver + - persistence_db_url=jdbc:postgresql://database:5432/sensorthings + - persistence_db_username=sensorthings + - persistence_db_password=ChangeMe + - persistence_autoUpdateDatabase=true + + mqtt: + image: fraunhoferiosb/frost-server-mqtt:latest + ports: + - 1883:1883 + - 9876:9876 + depends_on: + - database + - mosquitto + environment: + - serviceRootUrl=http://localhost:8080/FROST-Server + - bus_busImplementationClass=de.fraunhofer.iosb.ilt.sta.messagebus.MqttMessageBus + - bus_mqttBroker=tcp://mosquitto:1883 + - mqtt_CreateThreadPoolSize=10 + - mqtt_CreateMessageQueueSize=100 + - mqtt_SubscribeThreadPoolSize=20 + - mqtt_SubscribeMessageQueueSize=1000 + - persistence_persistenceManagerImplementationClass=de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong + - persistence_db_driver=org.postgresql.Driver + - persistence_db_url=jdbc:postgresql://database:5432/sensorthings + - persistence_db_username=sensorthings + - persistence_db_password=ChangeMe + + mosquitto: + image: eclipse-mosquitto + + database: + image: mdillon/postgis:latest + environment: + - POSTGRES_DB=sensorthings + - POSTGRES_USER=sensorthings + - POSTGRES_PASSWORD=ChangeMe + volumes: + - postgis_volume:/var/lib/postgresql/data + +volumes: + postgis_volume: diff --git a/docker-compose.yaml b/docker-compose.yaml index bfb93d7bb..a946c9411 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,6 +3,15 @@ version: '2' services: web: image: fraunhoferiosb/frost-server:latest + environment: + - serviceRootUrl=http://localhost:8080/FROST-Server + - http_cors_enable=true + - http_cors_allowed.origins=* + - persistence_db_driver=org.postgresql.Driver + - persistence_db_url=jdbc:postgresql://database:5432/sensorthings + - persistence_db_username=sensorthings + - persistence_db_password=ChangeMe + - persistence_autoUpdateDatabase=true ports: - 8080:8080 - 1883:1883 diff --git a/docs/architecture-packages.adoc b/docs/architecture-packages.adoc new file mode 100644 index 000000000..22f63edef --- /dev/null +++ b/docs/architecture-packages.adoc @@ -0,0 +1,39 @@ +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +endif::[] +:imagesdir: ../images/ +:xrefstyle: short + +== Package Architecture + +The FROST-Server comes in three packages, and thus also in three docker images: + +* https://hub.docker.com/r/fraunhoferiosb/frost-server/[fraunhoferiosb/frost-server] The all-in-one package +* https://hub.docker.com/r/fraunhoferiosb/frost-server-http/[fraunhoferiosb/frost-server-http] The HTTP-only package +* https://hub.docker.com/r/fraunhoferiosb/frost-server-mqtt/[fraunhoferiosb/frost-server-mqtt] The MQTT-only package + +=== All In One + +The all-in-one package contains both the HTTP and the MQTT parts of the FROST-Server. As such, it is the easiest way to get a server up and running. Because everything runs in the same JVM, the HTTP and MQTT parts can directly communicate, and there is only minimal delay between entities being updated, and MQTT messages being sent out (See <>). + +[#img-arch-single] +.Architecture using the All-In-One package +image::ArchitectureAllInOne.png[Architecture using the All-In-One package] + +The disadvantage is that everything runs in the same JVM. If the number of connections and requests increases, there is no way to scale this architecture up by adding more servers. You can add a second server next to the first one, and connect it to the same (replicated) database, but if an entity is added or updated through server one, the clients connected to the MQTT interface on server two never get notified. + +=== Separated HTTP & MQTT packages + +To make it possible to have multiple HTTP and MQTT instances, a message bus was introduced in the architecture (See <>). + +[#img-arch-multi] +.Architecture using separate MQTT and HTTP packages, and a message bus for communication +image::ArchitectureSeparated.png[Architecture using separate MQTT and HTTP packages, and a message bus for communication] + +This way, there can be an arbitrary number of HTTP and MQTT components, connecting to the same (replicated) database, and communicating among each other through the message bus. Currently there is only one implementation of the message bus connector, that uses an external MQTT server. + + diff --git a/docs/docker.adoc b/docs/docker.adoc new file mode 100644 index 000000000..abdb57ef9 --- /dev/null +++ b/docs/docker.adoc @@ -0,0 +1,35 @@ +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +endif::[] + +== Docker + +There's also the possibility to run FROST-Server and the needed database inside one or multiple Docker containers. +There are three docker images available: + +* https://hub.docker.com/r/fraunhoferiosb/frost-server/[fraunhoferiosb/frost-server] The all-in-one package +* https://hub.docker.com/r/fraunhoferiosb/frost-server-http/[fraunhoferiosb/frost-server-http] The HTTP-only package +* https://hub.docker.com/r/fraunhoferiosb/frost-server-mqtt/[fraunhoferiosb/frost-server-mqtt] The MQTT-only package + +To make deployment of these images easier, two example docker-compose files are provided. +The `docker-compose.yaml` file uses the all-in-one server, and adds the required postgresql / postgis database. +The `docker-compose-separated.yaml` file uses the separated HTTP and MQTT packages, +adds the required postgresql / postgis database, and adds a mosquitto server as a message bus. + +You can start the all-in-one docker image by running `docker-compose up`, or you can start the separated docker images using `docker-compose -f docker-compose-separated.yaml up`. +This will download the latest version of the specified FROST-Server packages, and any dependencies, and starts it all. +You can access the server by opening `http://localhost:8080/FROST-Server/` in your browser. + +If you want to build your own docker images, you can do this by calling: +---- +mvn dockerfile:build -pl FROST-Server.HTTP,FROST-Server.MQTT,FROST-Server.MQTTP +---- + +All data is stored inside the PostGIS database. To keep this state there's a volume automatically mapped to the PostGIS container. +For more information see the `docker-compose.yaml` file and the https://hub.docker.com/r/mdillon/postgis/[PostGIS container documentation] + +You can override all <> by using environment variables in the docker-compose files. diff --git a/docs/postgresql.adoc b/docs/postgresql.adoc new file mode 100644 index 000000000..98cddce7a --- /dev/null +++ b/docs/postgresql.adoc @@ -0,0 +1,104 @@ +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +endif::[] + +== Database: PostgreSQL with PostGIS + +The FROST-Server needs a database to store its data. Currently only https://www.postgresql.org/[PostgreSQL] with the https://postgis.net/[PostGIS] extensions is supported. + +=== Database installation + +1. create PostgreSQL database for the data +2. install the postgis extensions in this database (`CREATE EXTENSION postgis;`) +3. Optional: If you want to use UUIDs as entity ids, you need the uuid-ossp extension (`CREATE EXTENSION "uuid-ossp";`) + +=== Database configuration + +The HTTP and all-in-one MQTTP packages use JNDI to get a database resource from Tomcat or Wildfly. You have to configure a resource in Tomcat/Wildfly, and then tell the FROST-Server what name you gave this resource. The default name is `jdbc/sensorThings`. + +==== Tomcat + +1. Copy the https://repo.maven.apache.org/maven2/org/postgresql/postgresql/9.4.1212/postgresql-9.4.1212.jar[Postgres JDBC jar] and the https://repo.maven.apache.org/maven2/net/postgis/postgis-jdbc/2.2.1/postgis-jdbc-2.2.1.jar[postgis jar] to `$CATALINA_HOME/lib`. +2. Configure the database resource. Either in the Context, or elsewhere in server.xml: + + + +==== Wildfly + +. Create a directory `$WILDFLY_HOME/modules/org/postgresql/main` +. add both https://repo.maven.apache.org/maven2/org/postgresql/postgresql/9.4.1212/postgresql-9.4.1212.jar[Postgres JDBC jar] and the https://repo.maven.apache.org/maven2/net/postgis/postgis-jdbc/2.2.1/postgis-jdbc-2.2.1.jar[postgis jar] to it. +. Create a file named module.xml and add the following: ++ +[source,xml] +---- + + + + + + + + + + + +---- ++ +. Add a datasource to `$WILDFLY_HOME/standalone/configuration/[standalone.xml]` ++ +[source,xml] +---- + + + + jdbc:postgresql://localhost:5432/sensorthings + org.postgresql.Driver + postgres + + sensorthings + ChangeMe + + + + true + + + + + +---- + +=== Database initialisation or upgrade + +1. Choose which backend to use, by configuring the `persistence.persistenceManagerImplementationClass` option: + de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong:: Default value, using Long values for entity ids, generated in sequence + de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.PostgresPersistenceManagerString:: Using String values for entity ids, with new values generated using uuid_generate_v1mc() + de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.PostgresPersistenceManagerUuid:: Using uuid values for entity ids, with new values generated using uuid_generate_v1mc() +2. Browse to http://localhost:8080/FROST/DatabaseStatus +3. Click the upgrade button + +This should initialise/update the database to the latest version and the service is ready for use. + +### Performance and Indices + +By default, only primary and foreign keys have indices on them. If your database grows large +and you notice a significant slowdown, you should check which queries you use most, and +add indices for those queries. A very common one is probably for +Datastreams(x)/observations?$orderby=phenomenonTime asc + +[source,sql] +---- +CREATE INDEX "OBS-DS_ID-PHTIME_SE-O_ID" + ON "OBSERVATIONS" + USING btree + ("DATASTREAM_ID", "PHENOMENON_TIME_START" ASC, "PHENOMENON_TIME_END" ASC); +---- diff --git a/docs/settings.adoc b/docs/settings.adoc new file mode 100644 index 000000000..811d32d13 --- /dev/null +++ b/docs/settings.adoc @@ -0,0 +1,130 @@ +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +endif::[] + +[[settings]] +== Configuration Options + +There are several ways to deploy the different packages the make up the FROST-Server. +The HTTP and all-in-one MQTTP packages can be run in Tomcat or Wildfly, or as a docker image. +The MQTT package is a stand-alone application that can be run directly from the command line, or as a docker image. +For each option, the configuration is taken from (in order of priority): + +* Tomcat + . Enviroment variables + . The http://tomcat.apache.org/tomcat-8.0-doc/config/context.html[Context] entry either in server.xml, in `$CATALINA_BASE/conf/[enginename]/[hostname]/appname.xml` or in `/META-INF/context.xml` inside the war file. + . web.xml +* Wildfly + . Enviroment variables + . web.xml +* Standalone MQTT + . Enviroment variables + . The config file: FrostMqtt.properties + +It has the following options: + +TIP: Environment variables are not allowed to have the dot (`.`) character in the name. You must replace all of the dots in the names with an underscore (`_`). + + +=== General Settings + +These are settings affecting both the MQTT and HTTP packages. + +serviceRootUrl:: The base URL of the SensorThings Server without version. +defaultCount:: The default value for the $count query option. +defaultTop:: The default value for the $top query option. +maxTop:: The maximum allowed value for the $top query option. +useAbsoluteNavigationLinks:: If true, navigationLinks are absolute, otherwise relative. + + +=== HTTP settings + +These are settings for the HTTP package. + +http.cors.enable:: If true, a filter is added to allow cross-site-scripting. Defaults: `false`. +http.cors.allowed.origins:: A list of origins that are allowed to access the resource. A * can be specified to enable access to resource from any origin. Otherwise, a whitelist of comma separated origins can be provided. Eg: `http://www.w3.org, https://www.apache.org`. Defaults: `*`. +http.cors.allowed.methods:: A comma separated list of HTTP methods that can be used to access the resource, using cross-origin requests. These are the methods which will also be included as part of Access-Control-Allow-Methods header in pre-flight response. Eg: `GET, POST`. Defaults: `GET, HEAD, OPTIONS`. +http.cors.exposed.headers:: A comma separated list of headers other than simple response headers that browsers are allowed to access. These are the headers which will also be included as part of Access-Control-Expose-Headers header in the pre-flight response. Eg: `X-CUSTOM-HEADER-PING,X-CUSTOM-HEADER-PONG`. Default: `Location`. +http.cors.allowed.headers:: A comma separated list of request headers that can be used when making an actual request. These headers will also be returned as part of Access-Control-Allow-Headers header in a pre-flight response. Eg: `Origin,Accept`. Defaults: `Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization`. +http.cors.support.credentials:: A flag that indicates whether the resource supports user credentials. This flag is exposed as part of Access-Control-Allow-Credentials header in a pre-flight response. It helps browser determine whether or not an actual request can be made using credentials. Defaults: `false`. +http.cors.preflight.maxage:: The amount of seconds, browser is allowed to cache the result of the pre-flight request. This will be included as part of Access-Control-Max-Age header in the pre-flight response. A negative value will prevent CORS Filter from adding this response header to pre-flight response. Defaults: `1800`. +http.cors.request.decorate:: A flag to control if CORS specific attributes should be added to HttpServletRequest object or not. Defaults: `true`. + + +=== MQTT settings + +These are settings for the MQTT package. + +mqtt.mqttServerImplementationClass:: The java class used for running the MQTT server (must implement MqttServer interface) +mqtt.Enabled:: Specifies wether MQTT support will be enabled or not. +mqtt.Port:: The port the MQTT server runs on. +mqtt.QoS:: Quality of Service Level for MQTT messages. +mqtt.SubscribeMessageQueueSize:: Queue size for messages to be pubslihed via MQTT. +mqtt.SubscribeThreadPoolSize:: Number of threads use to dispatch MQTT notifications. +mqtt.CreateMessageQueueSize:: Queue size for create observation requests via MQTT . +mqtt.CreateThreadPoolSize:: Number of threads use to dispatch observation creation requests. +mqtt.Host:: The external IP address or host name the MQTT server should listen on. Set to 0.0.0.0 to listen on all interfaces. +mqtt.internalHost:: The internal host name of the MQTT server. +mqtt.WebsocketPort:: The port the MQTT server is reachable via WebSocket. +mqtt.maxInFlight:: The maximum number of "in-flight" messages to allow when sending notifications. +mqtt.WaitForEnter:: When true, and running in an interactive console, the FROST-MQTT component will read the keyboard input, and exit when the enter key is pressed. When false, the FROST-MQTT component has to be stopped by sending it a TERM Signal. + + +=== Persistence Settings + +These settings deal with the database connection, for both the HTTP and MQTT packages. + +persistence.persistenceManagerImplementationClass:: The java class used for persistence (must implement PersistenceManager interface). Current implementations are: + `de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong`::: Default value, using Long values for entity ids, generated in sequence. + `de.fraunhofer.iosb.ilt.sta.persistence.postgres.stringid.PostgresPersistenceManagerString`::: Using String values for entity ids, with new values generated using `uuid_generate_v1mc()`. + `de.fraunhofer.iosb.ilt.sta.persistence.postgres.uuidid.PostgresPersistenceManagerUuid`::: Using uuid values for entity ids, with new values generated using `uuid_generate_v1mc()`. +persistence.alwaysOrderbyId:: Always add an 'orderby=id asc' to queries to ensure consistent paging. +persistence.autoUpdateDatabase:: Automatically apply database updates. +persistence.idGenerationMode:: Determines how entity ids are generated. The three allowed values are: + `ServerGeneratedOnly`::: Default value, no client defined ids allowed, database generates ids. + `ServerAndClientGenerated`::: Both, server and client generated ids, are allowed. + `ClientGeneratedOnly`::: Client has to provide @iot.id to create entities. +persistence.db.jndi.datasource:: JNDI data source name, used when running in Tomcat/Wildfly. +persistence.db.driver:: The Database driver to use when not using JNDI. For PostgreSQL this should be: `org.postgresql.Driver` +persistence.db.url:: The database connection url when not using JNDI. Example: `jdbc:postgresql://localhost:5432/sensorthings` +persistence.db.username:: The username to use when connecting to the database when not using JNDI. +persistence.db.password:: The password to use when connecting to the database when not using JNDI. +persistence.db.conn.max:: The maximum number of database connections to use, when not using JNDI. +persistence.db.conn.idle.max:: The maximum number of idle database connections to keep open, when not using JNDI. +persistence.db.conn.idle.min:: The minimum number of idle database connections to keep open, when not using JNDI. + + +=== message bus settings + +These settings configure the way the HTTP and MQTT packages communicate with each other. + +bus.busImplementationClass:: + The java class that is used to connect to the message bus. Current implementations: + `de.fraunhofer.iosb.ilt.sta.messagebus.InternalMessageBus`::: An in-memory, in-jvm message bus, used when then MQTT and HTTP run in the same jvm in tomcat. + `de.fraunhofer.iosb.ilt.sta.messagebus.MqttMessageBus`::: A connector using MQTT as a message bus. + + +==== Settings for the Message bus class `de.fraunhofer.iosb.ilt.sta.messagebus.InternalMessageBus` + +This internal message bus can be used when all components run in the same JVM, as is the case with the all-in-one MQTTP package. + +bus.workerPoolSize:: The number of worker threads to handle sending messages to the bus. +bus.queueSize:: The size of the message queue to buffer messages to be sent to the bus. + + +==== Settings for the Message bus class `de.fraunhofer.iosb.ilt.sta.messagebus.MqttMessageBus` + +This message bus implementation connects to an MQTT server to exchange messages. + +bus.mqttBroker:: The MQTT broker to use as a message bus. +bus.sendWorkerPoolSize:: The number of worker threads to handle sending messages to the bus. +bus.sendQueueSize:: The size of the message queue to buffer messages to be sent to the bus. +bus.recvWorkerPoolSize:: The number of worker threads to handle messages coming from the bus. +bus.recvQueueSize:: The size of the message queue to buffer messages coming from the bus. +bus.topicName:: The MQTT topic to use as a message bus. +bus.qosLevel:: The Quality of Service Level for the MQTT bus. +bus.maxInFlight:: The maximum number of "in-flight" messages to allow on the MQTT bus. diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 000000000..9bbf7626c --- /dev/null +++ b/helm/README.md @@ -0,0 +1,9 @@ +# FROST-Server Helm charts + +This directory contains all [Helm](https://helm.sh/) charts associated to the [FROST-Server](https://github.com/FraunhoferIOSB/FROST-Server). + +The table bellow lists currently available Helm charts. + +Chart name | Description +----------------------------------- | ------------------------------------------------ +[frost-server](./frost-server) | Main Helm chart of the full FROST-Server stack \ No newline at end of file diff --git a/helm/build.sh b/helm/build.sh new file mode 100644 index 000000000..bc084812b --- /dev/null +++ b/helm/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +git config --global user.email "noReply@local" +git config --global user.name "Travis Build" + +# release version +if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_TAG}" != "" ]; then + echo "Building release helm chart" + git clone --quiet --branch master https://github.com/FraunhoferIOSB/helm-charts.git + /tmp/helm lint ./helm/frost-server + /tmp/helm package ./helm/frost-server -d ./helm-charts + /tmp/helm repo index --url https://fraunhoferiosb.github.io/helm-charts/ ./helm-charts +fi + +echo "Building snapshot helm chart" +git clone --quiet --branch master https://github.com/FraunhoferIOSB/helm-charts-snapshot.git +/tmp/helm lint ./helm/frost-server +/tmp/helm package ./helm/frost-server -d ./helm-charts-snapshot +/tmp/helm repo index --url https://fraunhoferiosb.github.io/helm-charts-snapshot/ ./helm-charts-snapshot + +echo "Helm chart build" diff --git a/helm/frost-server/.helmignore b/helm/frost-server/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/helm/frost-server/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/helm/frost-server/Chart.yaml b/helm/frost-server/Chart.yaml new file mode 100644 index 000000000..c53eb05ee --- /dev/null +++ b/helm/frost-server/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +name: frost-server +version: 1.9.3-snapshot +appVersion: 1.9-SNAPSHOT +description: The FROST-Server (FRaunhofer Opensource SensorThings-Server) is the first complete, open-source implementation of the OGC SensorThings API Part 1 (Sensing). +keywords: + - sensorthings-api + - sensorthings + - ogc + - sensing + - iot + - frost +home: https://github.com/FraunhoferIOSB/FROST-Server +icon: https://raw.githubusercontent.com/FraunhoferIOSB/FROST-Server/master/images/FROST-Server-darkgrey.png +sources: + - https://github.com/FraunhoferIOSB/FROST-Server +mainteners: + - name: Aurelien Bourdon + url: http://abourdon.github.io + - name: Philipp Hertweck + email: philipp.hertweck@iosb.fraunhofer.de diff --git a/helm/frost-server/README.md b/helm/frost-server/README.md new file mode 100644 index 000000000..72958ec0f --- /dev/null +++ b/helm/frost-server/README.md @@ -0,0 +1,230 @@ +# FROST-Server Helm chart + +The [FROST-Server](https://github.com/FraunhoferIOSB/FROST-Server) (**FR**aunhofer **O**pensource **S**ensor**T**hings-Server) is the first complete, open-source implementation of the [OGC SensorThings API Part 1 (Sensing)](http://docs.opengeospatial.org/is/15-078r6/15-078r6.html). + +## TL;DR + +Declare the Helm repo or update it + + $ helm repo add fraunhoferiosb https://fraunhoferiosb.github.io/helm-charts/ + $ helm repo update fraunhoferiosb + +Install the FROST-Server chart + + $ helm install fraunhoferiosb/frost-server + +## Introduction + +This chart bootstraps a [FROST-Server](https://github.com/FraunhoferIOSB/FROST-Server) deployment on a [Kubernetes](https://kubernetes.io/) cluster using the [Helm](https://helm.sh/) package manager. + +## Prerequisites + +- Have a [Kubernetes](https://kubernetes.io/) 1.4+ cluster. If you do not already have a cluster, you can: + - Create one by using [Minikube](https://kubernetes.io/docs/getting-started-guides/minikube) + - Or use [Katacoda](https://www.katacoda.com/courses/kubernetes/playground) + - Or use [Play with Kubernetes](http://labs.play-with-k8s.com/) +- Have the `kubectl` command-line tool correctly configured to communicate with your Kubernetes cluster +- Have the [`helm`](https://helm.sh/) command-line tool [correctly initialized with your Kubernetes cluster](https://docs.helm.sh/using_helm/#quickstart-guide) +- (Optionally but recommended) Have a [Ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress/) installed on your Kubernetes cluster. Need to have [Beta APIs](https://kubernetes.io/docs/reference/using-api/api-overview/) enabled. + +## Installing the Chart + +Before to go, declare the Helm repo or update it + + $ helm repo add fraunhoferiosb https://fraunhoferiosb.github.io/helm-charts/ + $ helm repo update fraunhoferiosb + +Then, to install the chart with the [release name](https://docs.helm.sh/using_helm/#quickstart-guide) `my-release` + + $ helm install --name my-release fraunhoferiosb/frost-server + +This command deploys FROST-Server on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. + +By default, the FROST-Server instance is reachable at the `http://frost-server:30080` URL (concatenation of the `frost.http.serviceHost` and `frost.http.ports.http.servicePort` configuration values). + +> **Warning**: Make sure to be able to resolve the `frost-server` DNS name by adding a rule either in your DNS server or in your local DNS resolver (e.g. `/etc/hosts` in Unix-based environments), or use an IP instead of a DNS name by setting the `frost.http.serviceHost` value. + +### Deployed FROST-Server resources + +This chart deploys a fully operational FROST-Server stack composed of: +- A (or several, depending on the number of replicas) FROST-Server's HTTP service(s) +- A (or several, depending on the number of replicas) FROST-Server's MQTT service(s) + - associated to an internal MQTT broker ([Eclipse Mosquitto](https://projects.eclipse.org/projects/technology.mosquitto)) +- (Not enabled by default) An internal FROST-Server's database + - associated to a local volume (disabled by default but can be enabled as explained [here](#about-persistence-support)) + +To have a view about the deployed FROST-Server resources in the `my-release` deployment execute: + + $ helm status my-release + +To visualize logs about deployed Helm release's pods, execute: + + $ kubeclt logs -l release=my-release + +Or, more precisely: + + $ kubeclt get pods -l release=my-release + $ kubectl logs + +Where `` is your desired pod name + +Or, even simpler, by using [kubetail](https://github.com/johanhaleby/kubetail): + + $ kubetail -l release=my-release + +## Uninstalling the Chart + +To uninstall/delete the `my-release` release: + + $ helm delete my-release + +This command removes all the Kubernetes components associated with the chart and deletes the release. + +## Configuration + +The following table lists the configurable parameters of the FROST-Server chart and their default values. + +Parameter | Description | Default +------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- +`name` | Override of the base name for any FROST-Server Kubernetes component | `nil` (use the chart name, `frost-server`, by default) +`frost.http.replicas` | Number of FROST-Server HTTP module replicas | `1` +`frost.http.ports.http.nodePort` | The external port (node port) of the FROST-Server HTTP service, **if not using Ingress** | `30080` +`frost.http.ports.http.servicePort` | The internal port of the FROST-Server HTTP module | `80` +`frost.http.ingress.enabled` | If Ingress needs to be enabled for the FROST-Server HTTP module. See [bellow](#ingress) for more information | `false` +`frost.http.serviceHost` | The host used by the [`serviceRootURL`](https://github.com/FraunhoferIOSB/FROST-Server/blob/master/docs/settings.adoc#general-settings) mandatory FROST-Server configuration parameter | `frost-server` +`frost.http.serviceProtocol` | The protocol where the host will be available | `http` +`frost.http.servicePort` | The external port of the FROST-Server HTTP module. If not set standard http(s) port is used, when ingress is enabled. Otherwise `frost.http.ports.http.nodePort` will be used. This value usefull when running a reverse proxy. | `nil` +`frost.http.urlSubPath` | The suffix added to the service url. This value is usefull when FROST-Server is not running in the root path of the doamin, e.g. when using a reverse proxy. | `nil` +`frost.http.defaultCount` | The default value for the $count query option used by the FROST-Server HTTP module | `false` +`frost.http.defaultTop` | The default value for the $top query option used by the FROST-Server HTTP module | `100` +`frost.http.maxTop` | The maximum allowed value for the $top query option used by the FROST-Server HTTP module | `1000` +`frost.http.useAbsoluteNavigationLinks` | If `true`, FROST-Server HTTP's `navigationLinks` are absolute, otherwise relative | `true` +`frost.http.db.autoUpdate` | Automatically apply database updates. | `true` +`frost.http.db.alwaysOrderbyId` | Always add an `orderby=id asc` to FROST-Server HTTP's database queries to ensure consistent paging | `false` +`frost.http.db.maximumConnection` | The maximum number of database connections used by the FROST-Server HTTP module | `10` +`frost.http.db.maximumIdleConnection` | The maximum number of idle database connections to keep open by the FROST-Server HTTP module | `10` +`frost.http.db.minimumIdleConnection` | The minimum number of idle database connections to keep open by the FROST-Server HTTP module | `10` +`frost.http.bus.sendWorkerPoolSize` | The number of FROST-Server HTTP worker threads to handle sending messages to the bus | `10` +`frost.http.bus.sendQueueSize` | The size of the FROST-Server HTTP message queue to buffer messages to be sent to the bus | `100` +`frost.http.bus.recvWorkerPoolSize` | The number of FROST-Server HTTP worker threads to handle messages coming from the bus | `10` +`frost.http.bus.maxInFlight` | The maximum number of FROST-Server HTTP _in-flight_ messages to allow on the MQTT bus | `50` +`frost.http.image.registry` | Image registry for the http module | `docker.io` +`frost.http.image.repository` | Image for the http module | `fraunhoferiosb/frost-server-http` +`frost.http.image.tag` | Imagetag for the http module | `{VERSION}` +`frost.http.image.pullPolicy` | Image pull policy for the http module | `IfNotPresent` +`frost.db.ports.postgresql.servicePort` | The internal port of the FROST-Server database service | `5432` +`frost.db.persistence.enabled` | If data persistence needs to be enabled. See [bellow](#persistence) for more information | `false` +`frost.db.persistence.existingClaim` | If set, then use an existing [PersistenceVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#lifecycle-of-a-volume-and-claim) for the FROST-Server database volume. See [bellow](#persistence) for more information | `nil` (use the builtin PersistenceVol +`frost.db.persistence.storageClassName` | The [StorageClassName](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class) to use by the FROST-Server database persistence. See [bellow](#persistence) for more information | `nil` (use the default StorageClass currently in use) +`frost.db.persistence.accessModes` | List of [AccessModes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) to claim if FROST-Server database persistence is enabled. See [bellow](#persistence) for more information | `{ReadWriteOnce}` +`frost.db.persistence.capacity` | The storage capacity required by the FROST-Server database persistence | `10Gi` +`frost.db.persistence.local.nodeMountPath` | The mount path to use if using the `local` StorageClassName as FROST-Server database StorageClass persistence. See [bellow](#persistence) for more information | `/mnt/frost-server-db` +`frost.db.database` | The FROST-Server database name to use | `sensorthings` +`frost.db.username` | The _base64_ username to use when connecting to the FROST-Server database | `c2Vuc29ydGhpbmdz` (`sensorthings`) +`frost.db.password` | The _base64_ password to use when connecting to the FROST-Server database | `bm93eW91Y2FuY2hhbmdlaXQ=` (`nowyoucanchangeit`) +`frost.db.idGenerationMode` | Determines how entity ids are generated by any FROST-Server module. See [here](https://github.com/FraunhoferIOSB/FROST-Server/blob/master/docs/settings.adoc#persistence-settings) for more information | `ServerGeneratedOnly` +`frost.db.implementationClass` | The Java class used for persistence by any FROST-Server module | `[...].PostgresPersistenceManagerLong` (see [1] bellow for the complete value) +`frost.db.image.registry` | Image registry for the database | `docker.io` +`frost.db.image.repository` | Image for the database | `mdillon/postgis` +`frost.db.image.tag` | Imagetag for the database | `10` +`frost.db.image.pullPolicy` | Image pull policy for the bus | `IfNotPresent` +`frost.mqtt.enabled` | If MQTT support needs to be enabled. See [bellow](#mqtt) for more information | `true` +`frost.mqtt.replicas` | The number of FROST-Server MQTT module replicas | `1` +`frost.mqtt.ports.mqtt.nodePort` | The external port (node port) of the FROST-Server MQTT service | `30883` +`frost.mqtt.ports.mqtt.servicePort` | The internal port of the FROST-Server MQTT service | `1883` +`frost.mqtt.ports.websocket.nodePort` | The external port (node port) of the FROST-Server MQTT websocket service | `30876` +`frost.mqtt.ports.websocket.servicePort` | The internal port of the FROST-Server MQTT websocket service | `9876` +`frost.mqtt.stickySessionTimeout` | Timeout (in seconds) of sticky time sessions used by the FROST-Server MQTT server | `10800` (3 hours) +`frost.mqtt.qos` | Quality of Service Level for MQTT messages | `2` +`frost.mqtt.subscribeMessageQueueSize` | Queue size for messages to be published via MQTT | `100` +`frost.mqtt.subscribeThreadPoolSize` | Number of threads use to dispatch MQTT notifications | `10` +`frost.mqtt.createMessageQueueSize` | Queue size for create observation requests via MQTT | `100` +`frost.mqtt.createThreadPoolSize` | Number of threads use to dispatch observation creation requests | `10` +`frost.mqtt.maxInFlight` | The maximum number of _in-flight_ messages to allow when sending notifications | `50` +`frost.mqtt.db.alwaysOrderbyId` | Always add an `orderby=id asc` to to FROST-Server MQTT's database queries to ensure consistent paging | `false` +`frost.mqtt.db.maximumConnection` | The maximum number of database connections to use by the FROST-Server MQTT module | `10` +`frost.mqtt.db.maximumIdleConnection` | The maximum number of idle database connections to keep open by the FROST-Server MQTT module | `10` +`frost.mqtt.db.minimumIdleConnection` | The minimum number of idle database connections to keep open by the FROST-Server MQTT module | `10` +`frost.mqtt.bus.sendWorkerPoolSize` | The number of FROST-Server MQTT worker threads to handle sending messages to the MQTT bus | `10` +`frost.mqtt.bus.sendQueueSize` | The size of the FROST-Server MQTT message queue to buffer messages to be sent to the MQTT bus | `100` +`frost.mqtt.bus.recvWorkerPoolSize` | The number of FROST-Server MQTT worker threads to handle messages coming from the MQTT bus | `10` +`frost.mqtt.bus.maxInFlight` | The maximum number of FROST-Server MQTT _in-flight_ messages to allow on the MQTT bus | `50` +`frost.mqtt.image.registry` | Image registry for the mqtt module | `docker.io` +`frost.mqtt.image.repository` | Image for the mqtt module | `fraunhoferiosb/frost-server-mqtt` +`frost.mqtt.image.tag` | Imagetag for the mqtt module | `{VERSION}` +`frost.mqtt.image.pullPolicy` | Image pull policy for the mqtt module | `IfNotPresent` +`frost.bus.ports.bus.servicePort` | The internal port of the FROST-Server Messages Bus service | `1883` +`frost.bus.implementationClass` | The Java class that is used to connect to the message bus, common for any FROST-Server modules | `[...].MqttMessageBus` (see [2] bellow for the complete value) +`frost.bus.topicName` | The MQTT topic to use as a message bus by any FROST-Server module | `FROST-Bus` +`frost.bus.qosLevel` | The Quality of Service Level for the MQTT bus by any FROST-Server module | `2` +`frost.bus.image.registry` | Image registry for the bus | `docker.io` +`frost.bus.image.repository` | Image for the bus | `eclipse-mosquitto` +`frost.bus.image.tag` | Imagetag for the bus | `1.4.12` +`frost.bus.image.pullPolicy` | Image pull policy for the bus | `IfNotPresent` + + +> [1] The complete default `frost.db.implementationClass` value is `de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong`. + +> [2] The complete default `frost.bus.implementationClass` value is `de.fraunhofer.iosb.ilt.sta.messagebus.MqttMessageBus`. + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm` `install|upgrade`. For example, + + $ helm install --name my-release \ + --set key_1=value_1,key_2=value_2 \ + fraunhoferiosb/frost-server + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + + # example for staging + $ helm install --name my-release -f values.yaml fraunhoferiosb/frost-server + +> **Tip**: You can use the default [values.yaml](./values.yaml) + +More information about the FROST-Server configuration can be found [here](https://github.com/FraunhoferIOSB/FROST-Server/blob/master/docs/settings.adoc). + +## MQTT + +As described in the [OGC SensorThings API specification](http://docs.opengeospatial.org/is/15-078r6/15-078r6.html#85), MQTT support is an optional extension but enabled by default in the FROST-Server Helm chart. +To disable MQTT support, override the `frost.mqtt.enabled` configuration value to `false`. + + $ helm install --set frost.mqtt.enabled=false fraunhoferiosb/frost-server + +## Persistence + +By default, the FROST-Server database is working without data persistence. Thus, if the Helm release or the FROST-Server database pod is deleted, then all saved data is lost. +To enable data persistence, turn on the `frost.db.persistence.enabled` configuration parameter: + + $ helm install --set frost.db.persistence.enabled=true fraunhoferiosb/frost-server + +Once FROST-Server database persistence is enabled, then the FROST-Server will either: +- use its own [PersistenceVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#lifecycle-of-a-volume-and-claim), which is described bellow +- or use an existing PersistenceVolumeClaim, if the `frost.db.persistence.existingClaim` is set + +If persistence is enabled and no existing PersistenceVolumeClaim is defined (`frost.db.persistence.existingClaim` is unset), then the FROST-Server chart will claim a [PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) that fits with its associated [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) name. +By default, no name is defined, then the default StorageClass currently in use in the Kubernetes cluster will be used. +But you can override this behaviour by setting the `frost.db.persistence.storageClassName` configuration value with your desired StorageClass name to use. + +If necessary, the FROST-Server chart also defines its own StorageClass name, `frost-server-db-local`, bound to a [builtin local volume](./templates/db-local-volume.yaml), which stores data in a local directory within the cluster (more precisely within the node where this local volume is deployed). +To enable it, set the `frost.db.persistence.storageClassName` to `frost-server-db-local` and precise the folder where data need to be persisted on the node + + $ helm install \ + --set frost.db.persistence.enabled=true,frost.db.persistence.storageClassName=frost-server-db-local,frost.db.persistence.local.nodeMountPath=/mnt/frost-server-db \ + fraunhoferiosb/frost-server + +> **Warning #1**: The `local` StorageClass cannot be scaled. + +> **Warning #2**: The `local` StorageClass can only be used if only the ReadWriteOnce [AccessMode](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) is claimed (check the `frost.db.persistence.accessModes` configuration parameter). + +## Ingress + +The FROST-Server HTTP component can be accessed through an [Ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress/). By default, Ingress is disabled but can be enabled thanks to the `frost.http.ingress.enabled` option: + + $ helm install --set frost.http.ingress.enabled=true fraunhoferiosb/frost-server + +Or if you want to enable it in your current living `my-release` release: + + $ helm upgrade --set frost.http.ingress.enabled=true my-release fraunhoferiosb/frost-server + +Once Ingress is enabled on the FROST-Server HTTP component, then the FROST-Server HTTP API can be accessed at `http://` (`http://frost-server` by default), on the standard 80 HTTP port, without being constrained to specify the `frost.http.ports.http.nodePort` port. + + > **Warning**: `frost.http.serviceHost` needs to be a DNS name. Make sure to be able to resolve it by adding a rule either in your DNS server or in your local DNS resolver (e.g. `/etc/hosts` in Unix-based environments). diff --git a/helm/frost-server/templates/NOTES.txt b/helm/frost-server/templates/NOTES.txt new file mode 100644 index 000000000..efe97f626 --- /dev/null +++ b/helm/frost-server/templates/NOTES.txt @@ -0,0 +1,37 @@ +Your FROST-Server v.{{ .Chart.AppVersion }} "{{ .Release.Name }}" release is now running. + +************************************************************************************************** + +IMPORTANT + + The FROST-Server HTTP API is reachable at {{ include "frost-server.http.serviceRootUrl" . }} + + In case of using a DNS name for the FROST-Server HTTP API host, make sure to be able to resolve it by + adding a rule either to your DNS server or your local DNS resolver (e.g. /etc/hosts in Unix-based environments). + + In case of a fresh database installation, you have to initialize it before using FROST-Server's services. + You can do either by: + - Browsing to {{ include "frost-server.http.serviceRootUrl" . }}/DatabaseStatus and clicking on the "Do Update" button + - Or executing a POST request to {{ include "frost-server.http.serviceRootUrl" . }}/DatabaseStatus +{{- if not .Values.frost.db.persistence.enabled }} + +/!\ WARNING /!\ + + FROST-Server database is working without persistence. You will lose your data when the FROST-Server database pod is terminated. + To enable persistence, set the "frost.db.persistence.enabled" configuration parameter to "true". +{{- end }} + +************************************************************************************************** + +Hereafter the list of available exposed FROST-Server's resources: + +FROST-Server's resource | Access URL +--------------------------- | ---------------------------------------------- +HTTP - Homepage | {{ include "frost-server.http.serviceRootUrl" . }} +HTTP - SensorThings API | {{ include "frost-server.http.serviceRootUrl" . }}/{{ include "frost-server.http.apiVersion" . }} +{{- if .Values.frost.mqtt.enabled }} +MQTT - TCP | {{ .Values.frost.http.serviceHost }}:{{ .Values.frost.mqtt.ports.mqtt.nodePort }} +MQTT - Websocket | {{ .Values.frost.http.serviceHost }}:{{ .Values.frost.mqtt.ports.websocket.nodePort }} +{{- end }} + +For more information about FROST-Server, please visit https://github.com/FraunhoferIOSB/FROST-Server \ No newline at end of file diff --git a/helm/frost-server/templates/_helpers.tpl b/helm/frost-server/templates/_helpers.tpl new file mode 100644 index 000000000..9853ddeab --- /dev/null +++ b/helm/frost-server/templates/_helpers.tpl @@ -0,0 +1,41 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "frost-server.name" -}} +{{- default .Chart.Name .Values.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "frost-server.fullName" -}} +{{- $name := default .Chart.Name .Values.name -}} +{{- if .tier -}} +{{- printf "%s-%s-%s" .Release.Name $name .tier | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "frost-server.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Get the HTTP service API version +*/}} +{{- define "frost-server.http.apiVersion" -}} +v1.0 +{{- end -}} + +{{/* +Get the HTTP service root URL +*/}} +{{- define "frost-server.http.serviceRootUrl" -}} +{{ .Values.frost.http.serviceProtocol }}://{{ .Values.frost.http.serviceHost }}{{ if .Values.frost.http.servicePort }}:{{ .Values.frost.http.servicePort }}{{ else if not .Values.frost.http.ingress.enabled }}:{{ .Values.frost.http.ports.http.nodePort }}{{ end }}{{ if .Values.frost.http.urlSubPath }}/{{ .Values.frost.http.urlSubPath }}{{ end }} +{{- end -}} diff --git a/helm/frost-server/templates/bus-deployment.yaml b/helm/frost-server/templates/bus-deployment.yaml new file mode 100644 index 000000000..9427ad5d8 --- /dev/null +++ b/helm/frost-server/templates/bus-deployment.yaml @@ -0,0 +1,37 @@ +{{- if .Values.frost.mqtt.enabled -}} +{{- $tier := "bus" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + matchLabels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + template: + metadata: + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + spec: + containers: + - name: {{ $fullName }} + image: "{{ .Values.frost.bus.image.registry }}/{{ .Values.frost.bus.image.repository }}:{{ .Values.frost.bus.image.tag }}" + imagePullPolicy: {{ .Values.frost.bus.image.pullPolicy | quote }} + ports: + - name: mqtt + containerPort: 1883 +{{- end -}} diff --git a/helm/frost-server/templates/bus-service.yaml b/helm/frost-server/templates/bus-service.yaml new file mode 100644 index 000000000..bac1edf45 --- /dev/null +++ b/helm/frost-server/templates/bus-service.yaml @@ -0,0 +1,25 @@ +{{- if .Values.frost.mqtt.enabled -}} +{{- $tier := "bus" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + ports: + - name: mqtt + port: {{ .Values.frost.bus.ports.bus.servicePort }} + targetPort: mqtt +{{- end -}} \ No newline at end of file diff --git a/helm/frost-server/templates/db-deployment.yaml b/helm/frost-server/templates/db-deployment.yaml new file mode 100644 index 000000000..ac24996bc --- /dev/null +++ b/helm/frost-server/templates/db-deployment.yaml @@ -0,0 +1,59 @@ +{{- $tier := "db" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + matchLabels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + template: + metadata: + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + spec: + containers: + - name: {{ $fullName }} + image: "{{ .Values.frost.db.image.registry }}/{{ .Values.frost.db.image.repository }}:{{ .Values.frost.db.image.tag }}" + imagePullPolicy: {{ .Values.frost.db.image.pullPolicy | quote }} + ports: + - name: postgresql + containerPort: 5432 + {{- if .Values.frost.db.persistence.enabled }} + volumeMounts: + - name: {{ $fullName }} + mountPath: /var/lib/postgresql/data + {{- end }} + env: + - name: POSTGRES_DB + value: {{ .Values.frost.db.database }} + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: {{ include "frost-server.fullName" . }} + key: db.username + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "frost-server.fullName" . }} + key: db.password + {{- if .Values.frost.db.persistence.enabled }} + volumes: + - name: {{ $fullName }} + persistentVolumeClaim: + claimName: {{ default $fullName .Values.frost.db.persistence.existingClaim }} + {{- end -}} diff --git a/helm/frost-server/templates/db-local-volume.yaml b/helm/frost-server/templates/db-local-volume.yaml new file mode 100644 index 000000000..dca475a2c --- /dev/null +++ b/helm/frost-server/templates/db-local-volume.yaml @@ -0,0 +1,26 @@ +{{- if .Values.frost.db.persistence.enabled -}} +{{- if .Values.frost.db.persistence.storageClassName -}} +{{- if eq .Values.frost.db.persistence.storageClassName "frost-server-db-local" -}} +{{- $tier := "db-local-volume" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + storageClassName: frost-server-db-local + capacity: + storage: {{ .Values.frost.db.persistence.capacity }} + accessModes: + - ReadWriteOnce + hostPath: + path: {{ .Values.frost.db.persistence.local.nodeMountPath }} +{{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/helm/frost-server/templates/db-service.yaml b/helm/frost-server/templates/db-service.yaml new file mode 100644 index 000000000..85b69214c --- /dev/null +++ b/helm/frost-server/templates/db-service.yaml @@ -0,0 +1,23 @@ +{{- $tier := "db" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + ports: + - name: postgresql + port: {{ .Values.frost.db.ports.postgresql.servicePort }} + targetPort: postgresql \ No newline at end of file diff --git a/helm/frost-server/templates/db-volume-claim.yaml b/helm/frost-server/templates/db-volume-claim.yaml new file mode 100644 index 000000000..2480ac353 --- /dev/null +++ b/helm/frost-server/templates/db-volume-claim.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.frost.db.persistence.enabled (not .Values.frost.db.persistence.existingClaim) -}} +{{- $tier := "db" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + {{- if .Values.frost.db.persistence.storageClassName }} + storageClassName: {{ .Values.frost.db.persistence.storageClassName }} + {{- end }} + resources: + requests: + storage: {{ .Values.frost.db.persistence.capacity }} + accessModes: + {{- range .Values.frost.db.persistence.accessModes }} + - {{ . }} + {{- end }} +{{- end -}} \ No newline at end of file diff --git a/helm/frost-server/templates/http-deployment.yaml b/helm/frost-server/templates/http-deployment.yaml new file mode 100644 index 000000000..de95a36e0 --- /dev/null +++ b/helm/frost-server/templates/http-deployment.yaml @@ -0,0 +1,123 @@ +{{- $tier := "http" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + matchLabels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + replicas: {{ .Values.frost.http.replicas }} + template: + metadata: + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + spec: + containers: + - name: {{ $fullName }} + image: "{{ .Values.frost.http.image.registry }}/{{ .Values.frost.http.image.repository }}:{{ .Values.frost.http.image.tag }}" + imagePullPolicy: {{ .Values.frost.http.image.pullPolicy | quote }} + ports: + - name: tomcat + containerPort: 8080 + env: + # Internal properties + - name: ApiVersion + value: {{ include "frost-server.http.apiVersion" . | quote }} + - name: serviceRootUrl + value: {{ include "frost-server.http.serviceRootUrl" . | quote }} + + # HTTP related properties + - name: defaultCount + value: "{{ .Values.frost.http.defaultCount }}" + - name: defaultTop + value: "{{ .Values.frost.http.defaultTop }}" + - name: maxTop + value: "{{ .Values.frost.http.maxTop }}" + - name: maxDataSize + value: "{{ .Values.frost.http.maxDataSize | int64 }}" + - name: useAbsoluteNavigationLinks + value: "{{ .Values.frost.http.useAbsoluteNavigationLinks }}" + + {{ if .Values.frost.mqtt.enabled -}} + # Messages bus related properties + - name: bus_mqttBroker + value: {{ printf "tcp://%s:1883" (include "frost-server.fullName" (merge (dict "tier" "bus") .)) | quote }} + - name: bus_busImplementationClass + value: "{{ .Values.frost.bus.implementationClass }}" + - name: bus_topicName + value: "{{ .Values.frost.bus.topicName }}" + - name: bus_qosLevel + value: "{{ .Values.frost.bus.qos }}" + - name: bus_sendWorkerPoolSize + value: "{{ .Values.frost.http.bus.sendWorkerPoolSize }}" + - name: bus_sendQueueSize + value: "{{ .Values.frost.http.bus.sendQueueSize }}" + - name: bus_recvWorkerPoolSize + value: "{{ .Values.frost.http.bus.recvWorkerPoolSize }}" + - name: bus_recvQueueSize + value: "{{ .Values.frost.http.bus.recvQueueSize }}" + - name: bus_maxInFlight + value: "{{ .Values.frost.http.bus.maxInFlight }}" + {{- end }} + + # Persistence related properties + - name: persistence_db_jndi_datasource + value: "" + - name: persistence_db_driver + value: "org.postgresql.Driver" + - name: persistence_db_url + value: {{ printf "jdbc:postgresql://%s:5432/%s" (include "frost-server.fullName" (merge (dict "tier" "db") .)) .Values.frost.db.database | quote }} + - name: persistence_persistenceManagerImplementationClass + value: "{{ .Values.frost.db.implementationClass }}" + - name: persistence_idGenerationMode + value: "{{ .Values.frost.db.idGenerationMode }}" + - name: persistence_autoUpdateDatabase + value: "{{ .Values.frost.http.db.autoUpdate }}" + - name: persistence_alwaysOrderbyId + value: "{{ .Values.frost.http.db.alwaysOrderbyId }}" + - name: persistence_db_conn_max + value: "{{ .Values.frost.http.db.maximumConnection }}" + - name: persistence_db_conn_idle_max + value: "{{ .Values.frost.http.db.maximumIdleConnection }}" + - name: persistence_db_conn_idle_min + value: "{{ .Values.frost.http.db.maximumIdleConnection }}" + - name: persistence_db_username + valueFrom: + secretKeyRef: + name: {{ include "frost-server.fullName" . }} + key: db.username + - name: persistence_db_password + valueFrom: + secretKeyRef: + name: {{ include "frost-server.fullName" . }} + key: db.password + + # Execute some operations before to launch the FROST-Server HTTP's WAR: + # 1. Remove all existing Tomcat webapps + # 2. Set the access to the FROST-Server HTTP API at the Tomcat's root + # 3. Clean environment (remove the unnecessary WAR) + command: + - /bin/sh + args: + - -c + - > + mv /usr/local/tomcat/webapps/FROST-Server.war /tmp + && rm -rf /usr/local/tomcat/webapps/* + && unzip -d /usr/local/tomcat/webapps/ROOT /tmp/FROST-Server.war + && rm /tmp/FROST-Server.war + && catalina.sh run diff --git a/helm/frost-server/templates/http-ingress.yaml b/helm/frost-server/templates/http-ingress.yaml new file mode 100644 index 000000000..99167d762 --- /dev/null +++ b/helm/frost-server/templates/http-ingress.yaml @@ -0,0 +1,23 @@ +{{- if .Values.frost.http.ingress.enabled -}} +{{- $tier := "http" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + rules: + - host: {{ .Values.frost.http.serviceHost }} + http: + paths: + - path: / + backend: + serviceName: {{ $fullName }} + servicePort: {{ .Values.frost.http.ports.http.servicePort }} +{{- end -}} \ No newline at end of file diff --git a/helm/frost-server/templates/http-service.yaml b/helm/frost-server/templates/http-service.yaml new file mode 100644 index 000000000..84eaef79e --- /dev/null +++ b/helm/frost-server/templates/http-service.yaml @@ -0,0 +1,29 @@ +{{- $tier := "http" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + {{- if not .Values.frost.http.ingress.enabled }} + type: NodePort + {{- end }} + ports: + - name: http + port: {{ .Values.frost.http.ports.http.servicePort }} + {{- if not .Values.frost.http.ingress.enabled }} + nodePort: {{ .Values.frost.http.ports.http.nodePort }} + {{- end }} + targetPort: tomcat \ No newline at end of file diff --git a/helm/frost-server/templates/mqtt-deployment.yaml b/helm/frost-server/templates/mqtt-deployment.yaml new file mode 100644 index 000000000..3375e0fd7 --- /dev/null +++ b/helm/frost-server/templates/mqtt-deployment.yaml @@ -0,0 +1,136 @@ +{{- if .Values.frost.mqtt.enabled -}} +{{- $tier := "mqtt" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + matchLabels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + replicas: {{ .Values.frost.mqtt.replicas }} + template: + metadata: + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + spec: + containers: + - name: {{ $fullName }} + image: "{{ .Values.frost.mqtt.image.registry }}/{{ .Values.frost.mqtt.image.repository }}:{{ .Values.frost.mqtt.image.tag }}" + imagePullPolicy: {{ .Values.frost.mqtt.image.pullPolicy | quote }} + ports: + - name: mqtt + containerPort: 1883 + - name: websocket + containerPort: 9876 + env: + # Internal properties + - name: ApiVersion + value: {{ include "frost-server.http.apiVersion" . | quote }} + - name: serviceRootUrl + value: {{ include "frost-server.http.serviceRootUrl" . | quote }} + + # HTTP related properties + - name: defaultCount + value: "{{ .Values.frost.http.defaultCount }}" + - name: defaultTop + value: "{{ .Values.frost.http.defaultTop }}" + - name: maxTop + value: "{{ .Values.frost.http.maxTop }}" + - name: maxDataSize + value: "{{ .Values.frost.http.maxDataSize | int64 }}" + - name: useAbsoluteNavigationLinks + value: "{{ .Values.frost.http.useAbsoluteNavigationLinks }}" + + # MQTT related properties + - name: mqtt_mqttServerImplementationClass + value: "de.fraunhofer.iosb.ilt.sensorthingsserver.mqtt.moquette.MoquetteMqttServer" + - name: mqtt_Enabled + value: "true" + - name: mqtt_Port + value: "1883" + - name: mqtt_Host + value: "0.0.0.0" + - name: mqtt_internalHost + value: "localhost" + - name: mqtt_WebsocketPort + value: "9876" + - name: mqtt_WaitForEnter + value: "false" + - name: mqtt_QoS + value: "{{ .Values.frost.mqtt.qos }}" + - name: mqtt_SubscribeMessageQueueSize + value: "{{ .Values.frost.mqtt.subscribeMessageQueueSize }}" + - name: mqtt_SubscribeThreadPoolSize + value: "{{ .Values.frost.mqtt.subscribeThreadPoolSize }}" + - name: mqtt_CreateMessageQueueSize + value: "{{ .Values.frost.mqtt.createMessageQueueSize }}" + - name: mqtt_CreateThreadPoolSize + value: "{{ .Values.frost.mqtt.createThreadPoolSize }}" + - name: mqtt_maxInFlight + value: "{{ .Values.frost.mqtt.maxInFlight }}" + + # Messages bus related properties + - name: bus_mqttBroker + value: {{ printf "tcp://%s:1883" (include "frost-server.fullName" (merge (dict "tier" "bus") .)) | quote }} + - name: bus_busImplementationClass + value: "{{ .Values.frost.bus.implementationClass }}" + - name: bus_topicName + value: "{{ .Values.frost.bus.topicName }}" + - name: bus_qosLevel + value: "{{ .Values.frost.bus.qos }}" + - name: bus_sendWorkerPoolSize + value: "{{ .Values.frost.mqtt.bus.sendWorkerPoolSize }}" + - name: bus_sendQueueSize + value: "{{ .Values.frost.mqtt.bus.sendQueueSize }}" + - name: bus_recvWorkerPoolSize + value: "{{ .Values.frost.mqtt.bus.recvWorkerPoolSize }}" + - name: bus_recvQueueSize + value: "{{ .Values.frost.mqtt.bus.recvQueueSize }}" + - name: bus_maxInFlight + value: "{{ .Values.frost.mqtt.bus.maxInFlight }}" + + # Persistence related properties + - name: persistence_db_jndi_datasource + value: "" + - name: persistence_db_driver + value: "org.postgresql.Driver" + - name: persistence_db_url + value: {{ printf "jdbc:postgresql://%s:5432/%s" (include "frost-server.fullName" (merge (dict "tier" "db") .)) .Values.frost.db.database | quote }} + - name: persistence_persistenceManagerImplementationClass + value: "{{ .Values.frost.db.implementationClass }}" + - name: persistence_idGenerationMode + value: "{{ .Values.frost.db.idGenerationMode }}" + - name: persistence_alwaysOrderbyId + value: "{{ .Values.frost.mqtt.db.alwaysOrderbyId }}" + - name: persistence_db_conn_max + value: "{{ .Values.frost.mqtt.db.maximumConnection }}" + - name: persistence_db_conn_idle_max + value: "{{ .Values.frost.mqtt.db.maximumIdleConnection }}" + - name: persistence_db_conn_idle_min + value: "{{ .Values.frost.mqtt.db.minimumIdleConnection }}" + - name: persistence_db_username + valueFrom: + secretKeyRef: + name: {{ include "frost-server.fullName" . }} + key: db.username + - name: persistence_db_password + valueFrom: + secretKeyRef: + name: {{ include "frost-server.fullName" . }} + key: db.password +{{- end -}} diff --git a/helm/frost-server/templates/mqtt-service.yaml b/helm/frost-server/templates/mqtt-service.yaml new file mode 100644 index 000000000..1596ac9b3 --- /dev/null +++ b/helm/frost-server/templates/mqtt-service.yaml @@ -0,0 +1,36 @@ +{{- if .Values.frost.mqtt.enabled -}} +{{- $tier := "mqtt" -}} +{{- $fullName := include "frost-server.fullName" (merge (dict "tier" $tier) .) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} +spec: + selector: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} + component: {{ $tier }} + type: NodePort + ports: + - name: mqtt + port: {{ .Values.frost.mqtt.ports.mqtt.servicePort }} + nodePort: {{ .Values.frost.mqtt.ports.mqtt.nodePort }} + targetPort: mqtt + - name: websocket + port: {{ .Values.frost.mqtt.ports.websocket.servicePort }} + nodePort: {{ .Values.frost.mqtt.ports.websocket.nodePort }} + targetPort: websocket + # MQTT server stores the subscriptions and the client should connect to the same server after the connection is lost + sessionAffinity: ClientIP + sessionAffinityConfig: + clientIP: + timeoutSeconds: {{ .Values.frost.mqtt.stickySessionTimeout }} +{{- end -}} \ No newline at end of file diff --git a/helm/frost-server/templates/secret.yaml b/helm/frost-server/templates/secret.yaml new file mode 100644 index 000000000..7c981caf8 --- /dev/null +++ b/helm/frost-server/templates/secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "frost-server.fullName" . }} + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + helm.sh/chart: {{ include "frost-server.chart" . }} + app: {{ include "frost-server.name" . }} +data: + db.username: {{ .Values.frost.db.username }} + db.password: {{ .Values.frost.db.password }} \ No newline at end of file diff --git a/helm/frost-server/values.yaml b/helm/frost-server/values.yaml new file mode 100644 index 000000000..4dec157a5 --- /dev/null +++ b/helm/frost-server/values.yaml @@ -0,0 +1,145 @@ +# If empty, use the chart name as common base name for any FROST-Server Kubernetes component +name: + +frost: + ########################################## + # FROST-Server HTTP module configuration # + ########################################## + http: + image: + registry: docker.io + repository: fraunhoferiosb/frost-server-http + tag: latest + pullPolicy: IfNotPresent + # FROST-Server HTTP deployment settings + replicas: 1 + ports: + http: + nodePort: 30080 + servicePort: 80 + ingress: + enabled: false + + # FROST-Server HTTP business settings + serviceHost: frost-server + serviceProtocol: http + servicePort: + urlSubPath: + defaultCount: false + defaultTop: 100 + maxTop: 1000 + maxDataSize: 25000000 + useAbsoluteNavigationLinks: true + + # FROST-Server Database related settings to the FROST-Server HTTP + db: + autoUpdate: true + alwaysOrderbyId: false + maximumConnection: 10 + maximumIdleConnection: 10 + minimumIdleConnection: 10 + + # FROST-Server Messages Bus related settings to the FROST-Server HTTP + bus: + sendWorkerPoolSize: 10 + sendQueueSize: 100 + recvWorkerPoolSize: 10 + recvQueueSize: 100 + maxInFlight: 50 + + ############################################## + # FROST-Server Database module configuration # + ############################################## + db: + image: + registry: docker.io + repository: mdillon/postgis + tag: 10 + pullPolicy: IfNotPresent + # FROST-Server Database deployment settings + ports: + postgresql: + servicePort: 5432 + persistence: + enabled: false + existingClaim: + storageClassName: + accessModes: + - ReadWriteOnce + capacity: 10Gi + # Uncomment bellow if you want to use the builtin "frost-server-db-local" StorageClass name. + # Only compatible if the ReadWriteOnce access mode is only claimed (check the frost.db.persistence.accessModes value). + # See project documentation for more information + #local: + # nodeMountPath: /mnt/frost-server-db + + # Common FROST-Server Database properties, shared by any other FROST-Server modules + implementationClass: "de.fraunhofer.iosb.ilt.sta.persistence.postgres.longid.PostgresPersistenceManagerLong" + idGenerationMode: "ServerGeneratedOnly" + database: sensorthings + username: c2Vuc29ydGhpbmdz + password: bm93eW91Y2FuY2hhbmdlaXQ= + + ########################################## + # FROST-Server MQTT module configuration # + ########################################## + mqtt: + image: + registry: docker.io + repository: fraunhoferiosb/frost-server-mqtt + tag: latest + pullPolicy: IfNotPresent + # FROST-Server MQTT deployment settings + enabled: true + replicas: 1 + ports: + mqtt: + nodePort: 30883 + servicePort: 1883 + websocket: + nodePort: 30876 + servicePort: 9876 + stickySessionTimeout: 10800 + + # FROST-Server MQTT business settings + qos: 2 + subscribeMessageQueueSize: 100 + subscribeThreadPoolSize: 10 + createMessageQueueSize: 100 + createThreadPoolSize: 10 + maxInFlight: 50 + waitForEnter: false + + # FROST-Server Database related settings to the FROST-Server MQTT + db: + alwaysOrderbyId: false + maximumConnection: 10 + maximumIdleConnection: 10 + minimumIdleConnection: 10 + + # FROST-Server Messages Bus related settings to the FROST-Server MQTT + bus: + sendWorkerPoolSize: 10 + sendQueueSize: 100 + recvWorkerPoolSize: 10 + recvQueueSize: 100 + maxInFlight: 50 + + ################################################## + # FROST-Server Messages Bus module configuration # + ################################################## + bus: + image: + registry: docker.io + repository: eclipse-mosquitto + tag: 1.4.12 + pullPolicy: IfNotPresent + # FROST-Server Messages Bus deployment settings + ports: + bus: + servicePort: 1883 + + # Common FROST-Server Messages Bus properties, shared by any other FROST-Server modules + implementationClass: "de.fraunhofer.iosb.ilt.sta.messagebus.MqttMessageBus" + topicName: "FROST-Bus" + qos: 2 diff --git a/helm/upload.sh b/helm/upload.sh new file mode 100644 index 000000000..4d6e68a0b --- /dev/null +++ b/helm/upload.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +./helm/build.sh + +# release version +if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_TAG}" != "" ]; then + cd helm-charts + git add . + git remote rm origin + git remote add origin https://phertweck:$GITHUB_API_KEY@github.com/FraunhoferIOSB/helm-charts + git commit -m "Travis build ${TRAVIS_BUILD_NUMBER} pushed" + git push origin master -fq + cd ../ + rm -rf ./helm-charts +fi + +# Only deploy master branch and tagged builds to snapshot repository +if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && ([ "${TRAVIS_BRANCH}" == "master" ] && [ "${TRAVIS_TAG}" != "" ]); then + cd helm-charts-snapshot + git add . + git remote rm origin + git remote add origin https://phertweck:$GITHUB_API_KEY@github.com/FraunhoferIOSB/helm-charts-snapshot + git commit -m "Travis build ${TRAVIS_BUILD_NUMBER} pushed" + git push origin master -fq + cd ../ + rm -rf ./helm-charts +fi + +echo "Helm chart build and pushed" diff --git a/images/ArchitectureAllInOne.png b/images/ArchitectureAllInOne.png new file mode 100644 index 000000000..84027e3c2 Binary files /dev/null and b/images/ArchitectureAllInOne.png differ diff --git a/images/ArchitectureSeparated.png b/images/ArchitectureSeparated.png new file mode 100644 index 000000000..13081a4be Binary files /dev/null and b/images/ArchitectureSeparated.png differ diff --git a/pom.xml b/pom.xml index 248831cf9..fe321a6e4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ de.fraunhofer.iosb.ilt.FROST-Server FROST-ServerParent - 1.6-SNAPSHOT + 1.9-SNAPSHOT pom FROST-ServerParent @@ -13,9 +13,12 @@ 2016 + FROST-Server.MQTTP FROST-Server.HTTP - FROST-Server.Core + FROST-Server.HTTP.Common + FROST-Server.MQTT FROST-Server.MQTT.Moquette + FROST-Server.Core FROST-Server.SQL FROST-Server.SQL.PGLong FROST-Server.SQL.PGString @@ -25,14 +28,25 @@ 1.3.1 0.6.8-uuid - 1.4.192 + 3.7 + 2.2.5 1.6 + 25.0-jre 4.5.2 + 2.1 2.7.1 + 7.0.4 + 2.6 2.9.2 4.12 3.4.2 1.1.7 + 3.8.0 + 3.1.1 + 2.5.3 + 3.0.1 + 3.2.2 + 1.2.0 2.2.1 9.4.1212 4.1.4 @@ -42,10 +56,6 @@ 1.3.7 ${project.build.directory}/endorsed - org.h2.Driver - jdbc:h2:${project.basedir}/target/builddb/h2 - - UTF-8 1.8 1.8 @@ -121,12 +131,24 @@ + + + SUREFIRE-1588 + + true + + + -Djdk.net.URLClassPath.disableClassPathURLCheck=true + + + + org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + ${maven-compiler-plugin.version} ${maven.compiler.source} ${maven.compiler.target} @@ -135,7 +157,7 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 + ${maven-source-plugin.version} attach-sources @@ -165,7 +187,7 @@ org.apache.maven.plugins maven-release-plugin - 2.5.3 + ${maven-release-plugin.version} diff --git a/setBranchVariable.sh b/setBranchVariable.sh new file mode 100644 index 000000000..177d43270 --- /dev/null +++ b/setBranchVariable.sh @@ -0,0 +1,15 @@ +#!/bin/bash +echo "Environment variable TRAVIS_BRANCH is" +echo $TRAVIS_BRANCH +version=$(mvn -Dexec.executable='echo' -Dexec.args='${project.version}' --non-recursive exec:exec -q) +echo "Recognized project verion:" +echo $version +if [ "${TRAVIS_BRANCH}" == "master" ]; then + export TAG=$version + echo "Recognized master branch. Tag is:" + echo $TAG +else + export TAG="${TRAVIS_BRANCH}-${version}" + echo "Recognized other branch than master. Tag is:" + echo $TAG +fi \ No newline at end of file diff --git a/travis-deploy-docker.sh b/travis-deploy-docker.sh new file mode 100644 index 000000000..be58acb4f --- /dev/null +++ b/travis-deploy-docker.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +mvn install && mvn dockerfile:build -pl FROST-Server.HTTP,FROST-Server.MQTT,FROST-Server.MQTTP +mvn dockerfile:tag@tag-version -pl FROST-Server.HTTP,FROST-Server.MQTT,FROST-Server.MQTTP + +if [ "${TRAVIS_BRANCH}" = "master" ]; then + mvn dockerfile:push@push-latest -Ddockerfile.useMavenSettingsForAuth=true -pl FROST-Server.HTTP,FROST-Server.MQTT,FROST-Server.MQTTP --settings travis-settings.xml +fi + +mvn dockerfile:push@push-version -Ddockerfile.useMavenSettingsForAuth=true -pl FROST-Server.HTTP,FROST-Server.MQTT,FROST-Server.MQTTP --settings travis-settings.xml \ No newline at end of file diff --git a/travis-deploy.sh b/travis-deploy.sh new file mode 100644 index 000000000..e247067e8 --- /dev/null +++ b/travis-deploy.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_TAG}" != "" ]; then + mvn deploy --settings travis-settings.xml +fi