diff --git a/.gitignore b/.gitignore index 7870c588b66e1a31f1cdbbacb3deef012fc86298..c7cd98121463908910aa8c7b3a85e1105bfa759c 100755 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1 @@ -HELP.md -target/ -mvnw -mvnw.cmd - -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ +/dataStorage_another/.idea/workspace.xml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..abbb83cde9dcba15c5f815850e2b10c22f416cee --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "dataStorage/shared"] + path = dataStorage/shared + url = ../shared.git +[submodule "openDataRetrieval/shared"] + path = openDataRetrieval/shared + url = ../shared.git diff --git a/dataStorage/.gitignore b/dataStorage/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7870c588b66e1a31f1cdbbacb3deef012fc86298 --- /dev/null +++ b/dataStorage/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +mvnw +mvnw.cmd + +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/dataStorage/.mvn/wrapper/MavenWrapperDownloader.java b/dataStorage/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000000000000000000000000000000000000..e76d1f3241d38db9b28f05133823bbed1ad289ff --- /dev/null +++ b/dataStorage/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/dataStorage/.mvn/wrapper/maven-wrapper.jar b/dataStorage/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 Binary files /dev/null and b/dataStorage/.mvn/wrapper/maven-wrapper.jar differ diff --git a/dataStorage/.mvn/wrapper/maven-wrapper.properties b/dataStorage/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..642d572ce90e5085986bdd9c9204b9404f028084 --- /dev/null +++ b/dataStorage/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/dataStorage/.mvn/wrapper/module-info.java b/dataStorage/.mvn/wrapper/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..485b276e70c65afcdfe1fac3b02f891c4f254d2d --- /dev/null +++ b/dataStorage/.mvn/wrapper/module-info.java @@ -0,0 +1,12 @@ +module gdsfg { + exports urbanite.prestojdbc; + exports urbanite.trafficmongopresto; + exports com.tecnalia.urbanite.storage; + exports com.tecnalia.urbanite.storage.transport.crowdflowobserved; + exports com.tecnalia.urbanite.storage.DB.Mongo; + exports com.tecnalia.urbanite.storage.transport.trafficflowobserved; + + requires java.logging; + requires java.sql; + requires org.apache.logging.log4j; +} \ No newline at end of file diff --git a/dataStorage/Dockerfile b/dataStorage/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..61a7578b3cbf8c3e00512c6ade6587646963bbac --- /dev/null +++ b/dataStorage/Dockerfile @@ -0,0 +1,33 @@ +FROM maven:3.6.0-jdk-8-slim AS builder + +WORKDIR /home/app/shared + +RUN mkdir -p /home/app/shared + +COPY shared/pom.xml /home/app/shared +RUN mvn dependency:go-offline + +COPY shared/src /home/app/shared/src +RUN mvn install + +WORKDIR /home/app + +COPY pom.xml . +RUN mvn dependency:go-offline + +COPY src src/ +RUN mvn clean package + +FROM openjdk:8-slim + +WORKDIR /root + +COPY --from=builder /home/app/target/dataStorage.jar /dataStorage.jar + +ENV MONGO_HOST=mongodb \ + MONGO_PORT=27017 \ + MONGO_DBNAME=urbanite \ + OPENTSDB_URL=http://opentsdb:4242 + +EXPOSE 80 +CMD ["java","-jar","/dataStorage.jar"] \ No newline at end of file diff --git a/dataStorage/README.md b/dataStorage/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d0e46e7d803226703ca6338559f56100d710118f --- /dev/null +++ b/dataStorage/README.md @@ -0,0 +1,67 @@ +# Data Storage and Retrieval Component +Microservice for inserting and retrieving data from the databases. + +## Table of Contents +1. [Clone](#clone) +2. [Push](#push) +1. [Build](#build) +1. [Docker](#docker) +1. [Configuration](#configuration) + 1. [Environment](#environment) +1. [API Reference](#api-reference) +1. [License](#license) + +## Clone +This project has a git submodule called 'shared'. Because of this, it is necessary to download the code including submodules. + +```bash +$ git clone --recurse-submodules <repo> +``` + +Once downloaded: +```bash +$ cd shared +$ mvn install +``` +## Push +Note that if you update the library 'shared', another project makes use of it, so you should update the commit of the other api project called 'open-data-retrieval'. + +## Build +Requirements: +* Docker + +## Docker + +Build docker image: + +```bash +$ docker build -t urbanite/datastorage . +``` + +Run docker image: + +```bash +$ docker run -it -p 80:80 urbanite/datastorage +``` + +## Configuration + +### Environment + +| Variable| Description | Default Value | +| :--- | :--- | :--- | +| `MONGO_HOST` | The IP of the host where MongoDB is installed. | `mongodb` | +| `MONGO_PORT` | The port where MongoDB is listening. | `27017` | +| `MONGO_DBNAME` | The name of the MongoDB Database to insert/retrive data. | `urbanite` | + +e.g. +```bash +$ docker run -it -p 80:80 -e MONGO_HOST=172.26.41.138 -e MONGO_PORT=27018 urbanite/datastorage +``` +## API Reference +When the component is deployed, the API reference is accessible from +http://\<server\>/data/swagger-ui/index.html?configUrl=/data/v3/api-docs/swagger-config + +## License + +[Apache License, Version 2.0](LICENSE.md) diff --git a/dataStorage/pom.xml b/dataStorage/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..914aba1e019e28f14f23674d3a7c7915e73cc75f --- /dev/null +++ b/dataStorage/pom.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.4.2</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.tecnalia.urbanite</groupId> + <artifactId>dataStorage</artifactId> + <version>1.3.1</version> + <name>dataStorage</name> + <description>URBANITE Data Storage and Retrieval Component</description> + <properties> + <java.version>1.8</java.version> + <log4j2.version>2.17.0</log4j2.version> <!-- https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot --> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <!-- Open API --> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.2.32</version> + </dependency> + <!-- JSON --> + <dependency> + <groupId>org.codehaus.jettison</groupId> + <artifactId>jettison</artifactId> + <version>1.3.7</version> + <type>jar</type> + </dependency> + <!-- Jenna --> + <dependency> + <groupId>org.apache.jena</groupId> + <artifactId>apache-jena-libs</artifactId> + <version>3.17.0</version> + <type>pom</type> + </dependency> + <!-- Mongo --> + <dependency> + <groupId>org.mongodb</groupId> + <artifactId>mongodb-driver-sync</artifactId> + </dependency> + + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + </dependency> + + <!-- https://mvnrepository.com/artifact/javax.activation/activation --> + <dependency> + <groupId>javax.activation</groupId> + <artifactId>activation</artifactId> + <version>1.1</version> + </dependency> + + <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime --> + <dependency> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + </dependency> + + <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </dependency> + <dependency> + <groupId>com.tecnalia.urbanite.storage</groupId> + <artifactId>shared</artifactId> + <version>1.0.0</version> + <scope>compile</scope> + </dependency> + + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <configuration> + <fork>true</fork> + <mainClass>${start-class}</mainClass> + </configuration> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addClasspath>true</addClasspath> + <mainClass>com.tecnalia.urbanite.storage.DataStorageApplication</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + <executions> + <execution> + <id>assemble-all</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/dataStorage/shared/.gitignore b/dataStorage/shared/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7870c588b66e1a31f1cdbbacb3deef012fc86298 --- /dev/null +++ b/dataStorage/shared/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +mvnw +mvnw.cmd + +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/dataStorage/shared/.mvn/wrapper/MavenWrapperDownloader.java b/dataStorage/shared/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000000000000000000000000000000000000..e76d1f3241d38db9b28f05133823bbed1ad289ff --- /dev/null +++ b/dataStorage/shared/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/dataStorage/shared/.mvn/wrapper/maven-wrapper.jar b/dataStorage/shared/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 Binary files /dev/null and b/dataStorage/shared/.mvn/wrapper/maven-wrapper.jar differ diff --git a/dataStorage/shared/.mvn/wrapper/maven-wrapper.properties b/dataStorage/shared/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..642d572ce90e5085986bdd9c9204b9404f028084 --- /dev/null +++ b/dataStorage/shared/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/dataStorage/shared/.mvn/wrapper/module-info.java b/dataStorage/shared/.mvn/wrapper/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..485b276e70c65afcdfe1fac3b02f891c4f254d2d --- /dev/null +++ b/dataStorage/shared/.mvn/wrapper/module-info.java @@ -0,0 +1,12 @@ +module gdsfg { + exports urbanite.prestojdbc; + exports urbanite.trafficmongopresto; + exports com.tecnalia.urbanite.storage; + exports com.tecnalia.urbanite.storage.transport.crowdflowobserved; + exports com.tecnalia.urbanite.storage.DB.Mongo; + exports com.tecnalia.urbanite.storage.transport.trafficflowobserved; + + requires java.logging; + requires java.sql; + requires org.apache.logging.log4j; +} \ No newline at end of file diff --git a/dataStorage/shared/pom.xml b/dataStorage/shared/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..9b144a297bb64cc708c5c8f1d0e21d90e8d11631 --- /dev/null +++ b/dataStorage/shared/pom.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.4.2</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.tecnalia.urbanite.storage</groupId> + <artifactId>shared</artifactId> + <version>1.0.0</version> + <description>URBANITE Data Storage and Retrieval Component</description> + <properties> + <java.version>1.8</java.version> + <log4j2.version>2.17.0</log4j2.version> <!-- https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot --> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.2.32</version> + </dependency> + <!-- JSON --> + <dependency> + <groupId>org.codehaus.jettison</groupId> + <artifactId>jettison</artifactId> + <version>1.3.7</version> + <type>jar</type> + </dependency> + <!-- Jenna --> + <dependency> + <groupId>org.apache.jena</groupId> + <artifactId>apache-jena-libs</artifactId> + <version>3.17.0</version> + <type>pom</type> + </dependency> + <!-- Mongo --> + <dependency> + <groupId>org.mongodb</groupId> + <artifactId>mongodb-driver-sync</artifactId> + </dependency> + + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + </dependency> + + <!-- https://mvnrepository.com/artifact/javax.activation/activation --> + <dependency> + <groupId>javax.activation</groupId> + <artifactId>activation</artifactId> + <version>1.1</version> + </dependency> + + <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime --> + <dependency> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + </dependency> + + <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </dependency> + + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addClasspath>true</addClasspath> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + <executions> + <execution> + <id>assemble-all</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/OpentsdbClient.java b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/OpentsdbClient.java new file mode 100644 index 0000000000000000000000000000000000000000..4cd8aa569ac125183c2a7bdf9d754fefde5f74ff --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/OpentsdbClient.java @@ -0,0 +1,74 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client; + +import com.tecnalia.opentsdb.client.query.Query; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; + +public class OpentsdbClient implements Closeable +{ + final private String urlBase; + final private CloseableHttpClient httpClient = HttpClients.createDefault(); + + public OpentsdbClient(String urlBase) + { + this.urlBase = urlBase; + } + + public HttpResponse Post(Point point) throws IOException + { + HttpPost post = new HttpPost(MessageFormat.format("{0}/api/put/?summary", urlBase)); + post.addHeader("content-type", "application/json"); + post.setEntity(new StringEntity(point.toJson())); + return this.httpClient.execute(post); + } + + public HttpResponse Get(Query query) throws IOException + { + HttpPost get = new HttpPost(MessageFormat.format("{0}/api/query", urlBase)); + get.addHeader("content-type", "application/json"); + get.setEntity(new StringEntity(query.toJson())); + return this.httpClient.execute(get); + } + + public HttpResponse Delete(String metric, String city, String idSpiral, long timestamp) throws IOException + { + String url = urlBase + "/api/query?start=" + timestamp + "&end=" + (timestamp + 1) // add 1 ms because the end has to be greather than the start + + "&m=avg:" + metric + URLEncoder.encode("{city="+ city +",id_spiral="+ idSpiral +"}", StandardCharsets.UTF_8.toString()); + + HttpDelete delete = new HttpDelete(url); + delete.addHeader("content-type", "application/json"); + return this.httpClient.execute(delete); + } + + @Override + public void close() throws IOException + { + this.httpClient.close(); + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/Point.java b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/Point.java new file mode 100644 index 0000000000000000000000000000000000000000..3bf3925706973f4e741c255afbcfb821b13a05f3 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/Point.java @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.HashMap; +import java.util.Map; + +public class Point +{ + private final String metric; + private final long timestamp; + private final Object value; + private Map<String, String> tags = new HashMap<String, String>(); + + private static final Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + Point(PointBuilder builder) + { + this.metric = builder.metric; + this.timestamp = builder.timestamp; + this.value = builder.value; + this.tags = builder.tags; + } + + public String toJson() + { + return gson.toJson(this); + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/PointBuilder.java b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/PointBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..13016d6b36b504a51025840ef892e701d700dacc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/PointBuilder.java @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client; + +import java.util.HashMap; +import java.util.Map; + +public class PointBuilder +{ + public String metric; + public long timestamp; + public double value; + public Map<String, String> tags = new HashMap<String, String>(); + + public PointBuilder addMetric(String metric) { + this.metric = metric; + return this; + } + + public PointBuilder addTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + public PointBuilder addValue(double value) { + this.value = value; + return this; + } + + public PointBuilder addTag(String key, String value) { + this.tags.put(key,value); + return this; + } + + public Point build() { + Point point = new Point(this); + validate(point); + return point; + } + + private void validate(Point point) { + //Do some basic validations to check + //if user object does not break any assumption of system + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetriQueryBuilder.java b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetriQueryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..f59b0f67ca8adc06025b4b321fbb5e4661b8d438 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetriQueryBuilder.java @@ -0,0 +1,54 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import java.util.Map; + +public class MetriQueryBuilder +{ + public String aggregator; + public String metric; + public String downsample; + public Map<String,String> tags; + + public MetriQueryBuilder addAggregator(String aggregator) + { + this.aggregator = aggregator.toLowerCase(); + return this; + } + + public MetriQueryBuilder addMetric(String metric) + { + this.metric = metric; + return this; + } + + public MetriQueryBuilder addDownsample(String downsample) + { + this.downsample = downsample; + return this; + } + + public MetriQueryBuilder addTags(Map<String,String> tags) + { + this.tags = tags; + return this; + } + + public MetricQuery build() { + return new MetricQuery(this); + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetricQuery.java b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetricQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..e74f2e265f4dbab169c35f62fe084d3c47dcadc5 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetricQuery.java @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import java.util.Map; + +public class MetricQuery +{ + public String aggregator; + public String metric; + public String downsample; + public Map<String,String> tags; + + public MetricQuery (MetriQueryBuilder builder) + { + this.aggregator = builder.aggregator; + this.metric = builder.metric; + this.downsample = builder.downsample; + this.tags = builder.tags; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/Query.java b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/Query.java new file mode 100644 index 0000000000000000000000000000000000000000..365d8fb4d69398d5cccfedc44681e30137d56e86 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/Query.java @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.List; + +public class Query +{ + public long start; + public long end; + public List<MetricQuery> queries; + + public Query(QueryBuilder builder) + { + this.start = builder.start; + this.end = builder.end; + this.queries = builder.queries; + } + + private static final Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + public String toJson() + { + return gson.toJson(this); + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/QueryBuilder.java b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/QueryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..c13b98aa4570cbcba299e8ff6263798288588fe8 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/opentsdb/client/query/QueryBuilder.java @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import java.util.ArrayList; +import java.util.List; + +public class QueryBuilder +{ + public long start; + public long end; + public List<MetricQuery> queries; + + public QueryBuilder() + { + this.queries = new ArrayList<>(); + } + + + public QueryBuilder addStart(long start) + { + this.start = start; + return this; + } + + public QueryBuilder addEnd(long end) + { + this.end = end; + return this; + } + + public QueryBuilder addMetricQuery(MetricQuery query) + { + this.queries.add(query); + return this; + } + + public Query build() { + return new Query(this); + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/APIResponse.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/APIResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..49abd5c1796d553b6a4911eb4292ca495a7765bb --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/APIResponse.java @@ -0,0 +1,93 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.springframework.http.HttpStatus; + +public class APIResponse { + private HttpStatus status = HttpStatus.OK; + private String error = ""; + private List<JSONObject> data = new ArrayList<JSONObject>(); + + public APIResponse(HttpStatus status, String error, List<JSONObject> data) { + super(); + this.status = status; + this.error = error; + this.data = data; + } + + public APIResponse(HttpStatus status, String error) { + super(); + this.status = status; + this.error = error; + this.data = new ArrayList<JSONObject>(); + } + + public APIResponse() { + super(); + } + + public APIResponse(HttpStatus status, List<JSONObject> data) { + super(); + this.status = status; + this.error = ""; + this.data = data; + } + + public HttpStatus getStatus() { + return status; + } + public void setStatus(HttpStatus status) { + this.status = status; + } + public String getError() { + return error; + } + public void setError(String error) { + this.error = error; + } + public List<JSONObject> getData() { + return data; + } + public void setData(List<JSONObject> data) { + this.data = data; + } + + @Override + public String toString() { + JSONObject res = new JSONObject(); + try { + res.put("status", status); + if (error.isEmpty() == false) + res.put("error", error); + else + res.put("data", data); + return res.toString(); + } catch (JSONException e) { + e.printStackTrace(); + } + + return "{\"status\":\"" + status + "\"}"; + } + + + +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/CustomRestExceptionHandler.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/CustomRestExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..6d5d4cb470d056957ad615fffb63577c3fedeec6 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/CustomRestExceptionHandler.java @@ -0,0 +1,202 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + + +import java.util.ArrayList; + +import java.util.List; + + +import javax.validation.ConstraintViolation; + +import javax.validation.ConstraintViolationException; + + +import com.tecnalia.urbanite.storage.APIResponse; +import org.springframework.http.HttpHeaders; + +import org.springframework.http.HttpStatus; + +import org.springframework.http.ResponseEntity; + +import org.springframework.validation.BindException; + +import org.springframework.validation.FieldError; + +import org.springframework.validation.ObjectError; + +import org.springframework.web.bind.MethodArgumentNotValidException; + +import org.springframework.web.bind.MissingServletRequestParameterException; + +import org.springframework.web.bind.annotation.ControllerAdvice; + +import org.springframework.web.bind.annotation.ExceptionHandler; + +import org.springframework.web.context.request.WebRequest; + +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + + +import com.tecnalia.urbanite.storage.Utils.Utils; + + +@ControllerAdvice + +public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler { + + + + @Override + + protected ResponseEntity<Object> handleMethodArgumentNotValid( + + MethodArgumentNotValidException ex, + + HttpHeaders headers, + + HttpStatus status, + + WebRequest request) { + + + List<String> errors = new ArrayList<String>(); + + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + + errors.add(error.getField() + ": " + error.getDefaultMessage()); + + } + + for (ObjectError error : ex.getBindingResult().getGlobalErrors()) { + + errors.add(error.getObjectName() + ": " + error.getDefaultMessage()); + + } + + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, errors.get(0)); + + return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request); + + } + + + @Override + + protected ResponseEntity<Object> handleBindException( + + BindException ex, + + HttpHeaders headers, + + HttpStatus status, + + WebRequest request) { + + + List<String> errors = new ArrayList<String>(); + + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + + errors.add(error.getField() + ": " + error.getDefaultMessage()); + + } + + for (ObjectError error : ex.getBindingResult().getGlobalErrors()) { + + errors.add(error.getObjectName() + ": " + error.getDefaultMessage()); + + } + + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, errors.get(0)); + + return handleExceptionInternal(ex, apiError, headers, status, request); + + + } + + + @ExceptionHandler({ ConstraintViolationException.class }) + + public ResponseEntity<Object> handleConstraintViolation( + + ConstraintViolationException ex, WebRequest request) { + + + List<String> errors = new ArrayList<String>(); + + for (ConstraintViolation<?> violation : ex.getConstraintViolations()) { + + errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": " + violation.getMessage()); + + } + + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, errors.get(0)); + + //return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus()); + + return Utils.formatResponse(apiError, true); + + } + + + + @ExceptionHandler({ MethodArgumentTypeMismatchException.class }) + + public ResponseEntity<Object> handleMethodArgumentTypeMismatch( + + MethodArgumentTypeMismatchException ex, + + WebRequest request) { + + + String error = "Invalid value '" + ex.getValue() + "' for parameter '" + ex.getName() +"'"; + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, error); + + //return new ResponseEntity<Object>(apiError.toString(), new HttpHeaders(), apiError.getStatus()); + + return Utils.formatResponse(apiError, true); + + } + + + @Override + + protected ResponseEntity<Object> handleMissingServletRequestParameter( + + MissingServletRequestParameterException ex, + + HttpHeaders headers, HttpStatus status, WebRequest request) { + + String error = "Parameter '" + ex.getParameterName() + "' is required."; + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, error); + + //return new ResponseEntity<Object>(apiError.toString(), new HttpHeaders(), apiError.getStatus()); + + return Utils.formatResponse(apiError, true); + + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/DBConfiguration.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/DBConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..50c2b31191574fb2a06b0ed21ab4228ef20b02f2 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/DBConfiguration.java @@ -0,0 +1,140 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DB; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class DBConfiguration { + + public enum DBTYPE { + MONGODB + /* + MYSQL + */ + } + private DBConfiguration() {} + + private static final Map<DBTYPE, DBParams> dbConfigurationsMap = new HashMap<>(); + private static final Logger logger = LoggerFactory.getLogger(DBConfiguration.class); + + public static void readDBsConfigurations(){ + + //mongoDB + String mongoHost = System.getenv("MONGO_HOST"); + if (mongoHost == null) { + logger.warn("Environment variable MONGO_HOST not found --> setting to default (\"localhost\")"); + mongoHost = "localhost"; + } + String mongoPort = System.getenv("MONGO_PORT"); + if (mongoPort == null) { + logger.warn("Environment variable MONGO_PORT not found --> setting to default (27017)"); + mongoPort = "27017"; + } + String mongoDBName= System.getenv("MONGO_DBNAME"); + if (mongoDBName == null) { + logger.warn("Environment variable MONGO_DBNAME not found --> setting to default (\"urbanite\")"); + mongoDBName = "urbanite"; + } + + DBParams params = new DBParams(mongoHost, mongoPort, "", "", mongoDBName); + dbConfigurationsMap.put(DBTYPE.MONGODB, params); + logger.debug("MongoDB connection settings: Server: " + mongoHost + ":" + mongoPort + "\t\tDataBase: " + mongoDBName); + + //other databases + + } + + public static DBParams getDBConfiguration(DBTYPE dbType) { + if (dbConfigurationsMap.get(dbType) == null) + //read it again (just in case it hadn't been done yet) + readDBsConfigurations(); + + return dbConfigurationsMap.get(dbType); + } + + public static String getOpentsdbUrl() + { + return System.getenv("OPENTSDB_URL") != null ? System.getenv("OPENTSDB_URL") : "http://localhost:4242"; + } + + public static class DBParams { + + private String host; + private String port; + private String user; + private String pass; + private String dbName; + + public DBParams() { + + } + + public DBParams(String host, String port, String user, String pass, String dbName) { + super(); + this.host = host; + this.port = port; + this.user = user; + this.pass = pass; + this.dbName = dbName; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getPort() { + return port; + } + + public void setPort(String port) { + this.port = port; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPass() { + return pass; + } + + public void setPass(String pass) { + this.pass = pass; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBManager.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBManager.java new file mode 100644 index 0000000000000000000000000000000000000000..12fc42f488face47dbf9ba7b3a9211a10fbc93dc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBManager.java @@ -0,0 +1,154 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DB; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.connection.ServerConnectionState; +import com.mongodb.connection.ServerDescription; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class MongoDBManager implements Closeable +{ + private final Logger logger = LoggerFactory.getLogger(MongoDBManager.class); + + private MongoClient mongoClient = null; + private MongoDatabase database = null; + private String mongoServer= ""; + private String mongoDBName = ""; + private MongoClientSettings clientSettings = null; + + public MongoDBManager(DBConfiguration.DBParams params) { + String host = params.getHost(); + String port = params.getPort(); + this.mongoServer = host + ":" + port; + //ignore user and pass + this.mongoDBName = params.getDbName(); + this.clientSettings = MongoClientSettings.builder().applyConnectionString(new ConnectionString("mongodb://" + mongoServer)) + .applyToSocketSettings(builder -> + builder.connectTimeout(120, java.util.concurrent.TimeUnit.SECONDS) + .readTimeout(120, java.util.concurrent.TimeUnit.SECONDS)) + .applyToClusterSettings(builder -> + builder.serverSelectionTimeout(120, java.util.concurrent.TimeUnit.SECONDS)) + .applyToConnectionPoolSettings(builder -> + builder.maxConnectionIdleTime(180, java.util.concurrent.TimeUnit.SECONDS)) + .build(); + } + + public boolean connect() { + + try { + mongoClient = MongoClients.create(clientSettings); + + //mongoClient = MongoClients.create("mongodb://" + mongoServer); + boolean connected = false; + boolean error = false; + while (!connected && !error){ + Thread.sleep(100); + List<ServerDescription> servers = mongoClient.getClusterDescription().getServerDescriptions(); + for (ServerDescription srv : servers) { + if (srv.getState() == ServerConnectionState.CONNECTED) connected = true; + if (srv.getException() != null) error = true; + } + + } + if (error) { + logger.error("Can't connect to server " + mongoServer); + } + if (connected) { + database = mongoClient.getDatabase(mongoDBName); + return true; + } + } catch (Exception e) { + logger.error("Can't connect to server " + mongoServer + " - " + e.getMessage()); + } + return false; + + } + + public void close() { + if (mongoClient != null) + mongoClient.close(); + } + + public MongoDatabase getDatabase() { + return database; + } + + public List<String> getModelCollectionNames(String collNamePrefix,SortingMode sort){ + List<String> collectionNames = new ArrayList<String>(); + + MongoIterable<String> colNames = database.listCollectionNames(); + + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix.toLowerCase())) + collectionNames.add(collectionName.toLowerCase()); + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(collectionNames); + else + Collections.sort(collectionNames, Collections.reverseOrder()); + return collectionNames; + } + + public List<String> getModelCollectionNamesBetweenYears(String collNamePrefix,Date startDate, Date endDate, SortingMode sort){ + List<String> collectionNames = new ArrayList<String>(); + + MongoIterable<String> colNames = database.listCollectionNames(); + int startYear = Utils.getDateYear(startDate); + int endYear = Utils.getDateYear(endDate); + + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix.toLowerCase())) { + try { + int year = Integer.parseInt(collectionName.substring(collNamePrefix.length())); + //no errors --> check if it's in range + boolean addToList = true; + if (startYear > 0 && year < startYear) addToList = false; + if (endYear > 0 && year > endYear) addToList = false; + if (addToList) + collectionNames.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR" format --> omit table + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(collectionNames); + else + Collections.sort(collectionNames, Collections.reverseOrder()); + return collectionNames; + } + + +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBUtils.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d3246657896cc561a5156d492d5fbf945ed31ce9 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBUtils.java @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DB; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.conversions.Bson; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mongodb.BasicDBObject; +import com.mongodb.client.model.Projections; + + +public class MongoDBUtils { + + + private static Logger logger = LoggerFactory.getLogger(MongoDBUtils.class); + + public static void addDateRangeFilter(BasicDBObject queryFilters, Date startDate, Date endDate, String dateSortAndRangeField) { + + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append(dateSortAndRangeField, dateRange); + } + + public static void addDateJSonFilter(BasicDBObject queryFilters, JSONObject filters) { + + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("MongoDBUtils::addDateJSonFilter::Error creating filters [" + filters + "]: " + e.getMessage()); + } + } + + public static Bson addReturnFields(List<String> returnFields,String dateSortAndRangeField ) { + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObservedFrom + if (returnFields.contains(dateSortAndRangeField) == false) returnFields.add(dateSortAndRangeField); + projection = Projections.fields(Projections.include(returnFields)); + } + return projection; + + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/AggregatorEnum.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/AggregatorEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..7d25d95d27f3b7472a754d0251290865153a7ee8 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/AggregatorEnum.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum AggregatorEnum +{ + MIN, + SUM, + MAX, + AVG, + DEV +} + + diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/City.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/City.java new file mode 100644 index 0000000000000000000000000000000000000000..183c27e1c8c0877ab33d6f1e6eb62b9e3ac5f259 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/City.java @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +import org.apache.commons.lang3.StringUtils; + +public enum City { + bilbao, + messina, + helsinki, + amsterdam; + + public static Boolean any(String id) + { + if(!StringUtils.isBlank(id)) + { + for(City city : City.values()) + { + if(id.equals(city.name())) + { + return true; + } + } + } + return false; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Address.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Address.java new file mode 100644 index 0000000000000000000000000000000000000000..c5a90fd26fe87056dc82d19c50c6fbcf06f7d5cd --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Address.java @@ -0,0 +1,108 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Address { + + @Schema(description = "The country. For example, Spain.", required = false, example = "") + private String addressCountry; + + @Schema(description = "The locality in which the street address is, and which is in the region.", required = false, example = "") + private String addressLocality; + + @Schema(description = "The region in which the locality is, and which is in the country.", required = false, example = "") + private String addressRegion; + + @Schema(description = "The geographic area where a service or offered item is provided.", required = false, example = "") + private String areaServed; + + @Schema(description = "The post office box number for PO box addresses.", required = false, example = "") + private String postOfficeBoxNumber; + + @Schema(description = "The postal code.", required = false, example = "") + private String postalCode; + + @Schema(description = "The street address.", required = false, example = "") + private String streetAddress; + + public String getAddressCountry() { + return addressCountry; + } + + public void setAddressCountry(String addressCountry) { + this.addressCountry = addressCountry; + } + + public String getAddressLocality() { + return addressLocality; + } + + public void setAddressLocality(String addressLocality) { + this.addressLocality = addressLocality; + } + + public String getAddressRegion() { + return addressRegion; + } + + public void setAddressRegion(String addressRegion) { + this.addressRegion = addressRegion; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getPostOfficeBoxNumber() { + return postOfficeBoxNumber; + } + + public void setPostOfficeBoxNumber(String postOfficeBoxNumber) { + this.postOfficeBoxNumber = postOfficeBoxNumber; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getStreetAddress() { + return streetAddress; + } + + public void setStreetAddress(String streetAddress) { + this.streetAddress = streetAddress; + } + + @Override + public String toString() { + return "{\"addressCountry\":\"" + addressCountry + "\", \"addressLocality\":\"" + addressLocality + + "\", \"addressRegion\":\"" + addressRegion + "\", \"areaServed\":\"" + areaServed + + "\", \"postOfficeBoxNumber\":\"" + postOfficeBoxNumber + "\", \"postalCode\":\"" + postalCode + + "\", \"streetAddress\":\"" + streetAddress + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Audience.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Audience.java new file mode 100644 index 0000000000000000000000000000000000000000..029e18442e7f39fa165139b0ecd3e8a36c3e8de6 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Audience.java @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum Audience { + adult, + allPublic, + children, + family, + senior, + teenager +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Category.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Category.java new file mode 100644 index 0000000000000000000000000000000000000000..f69661cc172409f0eedf1b263cdbeb6844c4f2bb --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Category.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum Category { + excursion, + gastronomy, + history, + museum, + outdoorActivities, + parksAndGardens, + religiousWorship, + shopping, + wellness +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CommonDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CommonDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..27da3185ce5d2e8bf9223708901c3b52425864b9 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CommonDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum CommonDataModel { + METADATA +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ContactPoint.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ContactPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..891a48fa00f1a418eb0fb4d248cc6cc1c393f434 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ContactPoint.java @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ContactPoint { + @Schema(description = "Property. Contact type of this item.", required = false, example = "") + private String contactType; + + @Schema(description = "Property.Email address of owner.", required = false, example = "") + private String email; + + @Schema(description = "Property. The name of this item.", required = false, example = "") + private String name; + + @Schema(description = "Property. Telephone of this contact.", required = false, example = "") + private String telephone; + + @Schema(description = "Property. URL which provides a description or further information about this item.", required = false, example = "") + private String url; + + public String getContactType() { + return contactType; + } + + public void setContactType(String contactType) { + this.contactType = contactType; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTelephone() { + return telephone; + } + + public void setTelephone(String telephone) { + this.telephone = telephone; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + @Override + public String toString() { + return "{\"contactType\":\"" + contactType + "\", \"email\":\"" + email + + "\", \"name\":\"" + name + "\", \"telephone\":\"" + telephone + + "\", \"url\":\"" + url + "\"}"; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CriticReview.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CriticReview.java new file mode 100644 index 0000000000000000000000000000000000000000..713839ea0675d7dfa09de9be5ca9e5f05c01bc03 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CriticReview.java @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class CriticReview { + @Schema(description = "", required = false) + private String language; + + @Schema(description = "", required = false) + private List<Review> reviews; + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public List<Review> getReviews() { + return reviews; + } + + public void setReviews(List<Review> reviews) { + this.reviews = reviews; + } + @Override + public String toString() { + return "{\"language\":\"" + language + "\", \"reviews\":\"" + reviews + + "\"}"; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Currency.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Currency.java new file mode 100644 index 0000000000000000000000000000000000000000..f376fca910d4614bcdc8e7f29faaac903b83817f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Currency.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum Currency { + EUR, + USD +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DayOfWeek.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DayOfWeek.java new file mode 100644 index 0000000000000000000000000000000000000000..3c8a149c397c217a5ea64df470fdf303753a51d8 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DayOfWeek.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum DayOfWeek { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, + PublicHolidays +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Dimension.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Dimension.java new file mode 100644 index 0000000000000000000000000000000000000000..ab4eee62a8b85d4ad1fed26230c553f91a45956d --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Dimension.java @@ -0,0 +1,58 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Dimension { + @Schema(required = false) + private Double depth; + + @Schema(required = false) + private Double height; + + @Schema(required = false) + private Double width; + + public Double getDepth() { + return depth; + } + + public void setDepth(Double depth) { + this.depth = depth; + } + + public Double getHeight() { + return height; + } + + public void setHeight(Double height) { + this.height = height; + } + + public Double getWidth() { + return width; + } + + public void setWidth(Double width) { + this.width = width; + } + + @Override + public String toString() { + return "{\"depth\":" + depth + ", \"height\":" + height + ", \"width\":" + width+ "}"; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/District.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/District.java new file mode 100644 index 0000000000000000000000000000000000000000..fb7dc0eac59660aaa3f414b9f20592bcbfb143bc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/District.java @@ -0,0 +1,78 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.HashMap; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class District { + + @Schema(description = "residents", required = false) + private DoubleValue residents; + + @Schema(description = "families", required = false) + private DoubleValue families; + + @Schema(description = "males", required = false) + private DoubleValue males; + + @Schema(description = "females", required = false) + private DoubleValue females; + + @Schema(description = "ages", required = false) + private HashMap<String, MaleFemale> ages; + + public DoubleValue getResidents() { + return residents; + } + + public void setResidents(DoubleValue residents) { + this.residents = residents; + } + + public DoubleValue getFamilies() { + return families; + } + + public void setFamilies(DoubleValue families) { + this.families = families; + } + + public DoubleValue getMales() { + return males; + } + + public void setMales(DoubleValue males) { + this.males = males; + } + + public DoubleValue getFemales() { + return females; + } + + public void setFemales(DoubleValue females) { + this.females = females; + } + + public HashMap<String, MaleFemale> getAges() { + return ages; + } + + public void setAges(HashMap<String, MaleFemale> ages) { + this.ages = ages; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DoubleValue.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DoubleValue.java new file mode 100644 index 0000000000000000000000000000000000000000..104004661f0792b4701eaa37cf2114bfe7c103cf --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DoubleValue.java @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class DoubleValue { + + @Schema(description = "number, percentage,..", required = false) + private String type; + + @Schema(description = "", required = false) + private Double value; + + @Schema(description = "inhabitants/km^2,...", required = false) + private String unit; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ElectricTransport.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ElectricTransport.java new file mode 100644 index 0000000000000000000000000000000000000000..442d62aabdad6aa108a7f24cedfe39e0db80f430 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ElectricTransport.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum ElectricTransport { + electricBicycle, + electricCar, + electricMotorBike, + electricScooter +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/InstallationMode.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/InstallationMode.java new file mode 100644 index 0000000000000000000000000000000000000000..314cb964079d53639b605679d04b6303e5824c88 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/InstallationMode.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum InstallationMode { + aerial, + ground, + underGround, + underSea +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Inventory.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Inventory.java new file mode 100644 index 0000000000000000000000000000000000000000..8c304a8570631f02cb34f1f9330c00e006124f4c --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Inventory.java @@ -0,0 +1,77 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Inventory { + @Schema(required = false) + private List<PlatformType> items; + + @Schema(required = false) + private Double nbOfIOPoint; + + @Schema(required = false) + private Double nbOfLane; + + @Schema(required = false) + private Double nbOfPlatform; + + public List<PlatformType> getItems() { + return items; + } + + public void setItems(List<PlatformType> items) { + this.items = items; + } + + public Double getNbOfIOPoint() { + return nbOfIOPoint; + } + + public void setNbOfIOPoint(Double nbOfIOPoint) { + this.nbOfIOPoint = nbOfIOPoint; + } + + public Double getNbOfLane() { + return nbOfLane; + } + + public void setNbOfLane(Double nbOfLane) { + this.nbOfLane = nbOfLane; + } + + public Double getNbOfPlatform() { + return nbOfPlatform; + } + + public void setNbOfPlatform(Double nbOfPlatform) { + this.nbOfPlatform = nbOfPlatform; + } + @Override + public String toString() { + String inventory ="{\"PlatformType\":["; + for (PlatformType pt:items) inventory=inventory+"\""+pt+"\","; + inventory = inventory.substring(0, inventory.length()-1); + inventory = inventory+"], \"nbOfIOPoint\":"+nbOfIOPoint+",\"nbOfLane\":"+nbOfLane+",\"nbOfPlatform\":"+nbOfPlatform+"\"}"; + return inventory; + + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Itinerary.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Itinerary.java new file mode 100644 index 0000000000000000000000000000000000000000..e2ea011b5d742dacf0c3659a26cf99fc3681a558 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Itinerary.java @@ -0,0 +1,78 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Itinerary { + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "", required = false) + private String image; + + @Schema(description = "", required = false) + private String name; + + @Schema(description = "", required = false) + private Integer position; + + @Schema(description = "", required = false) + private List<String>streetAddress; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(Integer position) { + this.position = position; + } + + public List<String> getStreetAddress() { + return streetAddress; + } + + public void setStreetAddress(List<String> streetAddress) { + this.streetAddress = streetAddress; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationGeojson.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationGeojson.java new file mode 100644 index 0000000000000000000000000000000000000000..d96a644b30768a1d6148356c5eba5db4efc04353 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationGeojson.java @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public abstract class LocationGeojson { + + private List<Double> bbox; + private String type; + + + public List<Double> getBbox() { + return bbox; + } + public void setBbox(List<Double> bbox) { + this.bbox = bbox; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public LocationGeojson() { + super(); + } + + @Override + public String toString() { + String locationGeojson ="{\"bbox\":["; + for ( Double pt:bbox) locationGeojson=locationGeojson+""+pt+","; + locationGeojson = locationGeojson.substring(0, locationGeojson.length()-1); + locationGeojson = locationGeojson+"], \"type\":\""+type+"\"}"; + return locationGeojson; + + + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationLineString.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationLineString.java new file mode 100644 index 0000000000000000000000000000000000000000..3bd9128860fe5fe8d902c6b024bf4d5679387faf --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationLineString.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationLineString extends LocationGeojson{ + + private List<List<Double>> coordinates; + + public LocationLineString() { + super(); + this.setType("LineString"); + } + + public List<List<Double>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<Double>> coordinates) { + this.coordinates = coordinates; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiLineString.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiLineString.java new file mode 100644 index 0000000000000000000000000000000000000000..49a9704f8c5bcc762e557e6190bfe89a77a1f33f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiLineString.java @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationMultiLineString extends LocationGeojson{ + + private List<List<List<Double>>> coordinates; + + public LocationMultiLineString() { + super(); + this.setType("MultiLineString"); + } + + public List<List<List<Double>>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<List<Double>>> coordinates) { + this.coordinates = coordinates; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPoint.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..7f1dcc3bba0dc7e4a71916c947202564ac524020 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPoint.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationMultiPoint extends LocationGeojson{ + + private List<List<Double>> coordinates; + + public LocationMultiPoint() { + super(); + this.setType("MultiPoint"); + } + + public List<List<Double>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<Double>> coordinates) { + this.coordinates = coordinates; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPolygon.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPolygon.java new file mode 100644 index 0000000000000000000000000000000000000000..5b016f43b82ba7d0c548822c8d2fba86e3e0d794 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPolygon.java @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationMultiPolygon extends LocationGeojson{ + + private List<List<List<List<Double>>>> coordinates; + + public LocationMultiPolygon() { + super(); + this.setType("MultiPolygon"); + } + + public List<List<List<List<Double>>>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<List<List<Double>>>> coordinates) { + this.coordinates = coordinates; + } + + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPoint.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..e71ebbb2ac41183720bc1fed827e7e47cb7eb069 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPoint.java @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationPoint extends LocationGeojson{ + + private List<Double> coordinates; + + public LocationPoint() { + super(); + this.setType("Point"); + } + + public List<Double> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<Double> coordinates) { + this.coordinates = coordinates; + } + + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPolygon.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPolygon.java new file mode 100644 index 0000000000000000000000000000000000000000..de52b18c907dbcd2936f6dcdc66b34e261508259 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPolygon.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationPolygon extends LocationGeojson{ + + private List<List<List<Double>>> coordinates; + + public LocationPolygon() { + super(); + this.setType("Polygon"); + } + + public List<List<List<Double>>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<List<Double>>> coordinates) { + this.coordinates = coordinates; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/MaleFemale.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/MaleFemale.java new file mode 100644 index 0000000000000000000000000000000000000000..f23f639a6a9ba183ac8b851d92094e3febc2765c --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/MaleFemale.java @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import com.google.gson.annotations.SerializedName; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class MaleFemale { + @SerializedName("M") + @Schema(description = "", name = "M",required = false) + private Double M; + + @Schema(description = "", name = "F",required = false) + @SerializedName("F") + private Double F; + + public Double getM() { + return M; + } + public void setM(Double m) { + M = m; + } + public Double getF() { + return F; + } + public void setF(Double f) { + F = f; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/OpeningHoursSpecification.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/OpeningHoursSpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..05eb1e2b5779e5fe1f329c3ea1e1215ec08e970e --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/OpeningHoursSpecification.java @@ -0,0 +1,83 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class OpeningHoursSpecification { + @Schema(description = "", required = false) + private String closes; + + @Schema(description = "", required = false) + private DayOfWeek dayOfWeek; + + @Schema(description = "", required = false) + private String opens; + + @Schema(description = "", required = false) + private String validFrom; + + @Schema(description = "", required = false) + private String validThrough; + + public String getCloses() { + return closes; + } + + public void setCloses(String closes) { + this.closes = closes; + } + + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + public String getOpens() { + return opens; + } + + public void setOpens(String opens) { + this.opens = opens; + } + + public String getValidFrom() { + return validFrom; + } + + public void setValidFrom(String validFrom) { + this.validFrom = validFrom; + } + + public String getValidThrough() { + return validThrough; + } + + public void setValidThrough(String validThrough) { + this.validThrough = validThrough; + } + @Override + public String toString() { + + String retorno= "{\"closes\":" +closes + ", \"dayOfWeek\":\"" + dayOfWeek + + "\", \"opens\":\"" + opens + "\", \"validFrom\":" + validFrom + + ", \"validThrough\":\"" + validThrough + "\"}"; + return retorno; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PaymentAccepted.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PaymentAccepted.java new file mode 100644 index 0000000000000000000000000000000000000000..721f62206dda38adac23052cd8c0a82e88393681 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PaymentAccepted.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum PaymentAccepted { + Cash, + CreditCard, + CryptoCurrency, + other +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Pitch.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Pitch.java new file mode 100644 index 0000000000000000000000000000000000000000..4572d0e17a08b3f4f5acfc724863c6e9e9268e15 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Pitch.java @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Pitch { + + @Schema(description = "", required = false) + private String article; + + @Schema(description = "", required = false) + private String language; + + public String getArticle() { + return article; + } + + public void setArticle(String article) { + this.article = article; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PlatformType.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PlatformType.java new file mode 100644 index 0000000000000000000000000000000000000000..ed9f8812b20dd984964f0d44398127cecb10ba5d --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PlatformType.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum PlatformType { + lateral, + central +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PopulationSummary.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PopulationSummary.java new file mode 100644 index 0000000000000000000000000000000000000000..700dc3eafab64e0c908df4977ee1affcf9e239c3 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PopulationSummary.java @@ -0,0 +1,76 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PopulationSummary { + + @Schema(description = "", name = "population-summary",required = false) + private DoubleValue metropolitanAareaInahitants; + + @Schema(description = "", name = "city-inhabitants",required = false) + private DoubleValue cityInhabitants; + + @Schema(description = "", name = "population-density",required = false) + private DoubleValue populationDensity; + + @Schema(description = "", required = false) + private DoubleValue males; + + @Schema(description = "",required = false) + private DoubleValue females; + + public DoubleValue getMetropolitanAareaInahitants() { + return metropolitanAareaInahitants; + } + + public void setMetropolitanAareaInahitants(DoubleValue metropolitanAareaInahitants) { + this.metropolitanAareaInahitants = metropolitanAareaInahitants; + } + + public DoubleValue getCityInhabitants() { + return cityInhabitants; + } + + public void setCityInhabitants(DoubleValue cityInhabitants) { + this.cityInhabitants = cityInhabitants; + } + + public DoubleValue getPopulationDensity() { + return populationDensity; + } + + public void setPopulationDensity(DoubleValue populationDensity) { + this.populationDensity = populationDensity; + } + + public DoubleValue getMales() { + return males; + } + + public void setMales(DoubleValue males) { + this.males = males; + } + + public DoubleValue getFemales() { + return females; + } + + public void setFemales(DoubleValue females) { + this.females = females; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PriceSpecification.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PriceSpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..c6e3db4f06fa5fcd99433a21e0ddf5312c200a62 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PriceSpecification.java @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PriceSpecification { + @Schema(description = "", required = false) + private List<String> audience; + + @Schema(description = "", required = false) + private Double eligibleQuantity; + + @Schema(description = "", required = false) + private Double maxPrice; + + @Schema(description = "", required = false) + private Double minPrice; + + @Schema(description = "", required = false) + private Double price; + + public List<String> getAudience() { + return audience; + } + + public void setAudience(List<String> audience) { + this.audience = audience; + } + + public Double getEligibleQuantity() { + return eligibleQuantity; + } + + public void setEligibleQuantity(Double eligibleQuantity) { + this.eligibleQuantity = eligibleQuantity; + } + + public Double getMaxPrice() { + return maxPrice; + } + + public void setMaxPrice(Double maxPrice) { + this.maxPrice = maxPrice; + } + + public Double getMinPrice() { + return minPrice; + } + + public void setMinPrice(Double minPrice) { + this.minPrice = minPrice; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Review.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Review.java new file mode 100644 index 0000000000000000000000000000000000000000..9b6404cfc25a5f0df0db43c53dae89f0b2cf0b45 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Review.java @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Review { + @Schema(description = "", required = false) + private String article; + + @Schema(description = "", required = false) + private String origin; + + @Schema(description = "", required = false) + private Integer ratingValue; + + @Schema(description = "", required = false) + private Integer starRating; + + public String getArticle() { + return article; + } + + public void setArticle(String article) { + this.article = article; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public Integer getRatingValue() { + return ratingValue; + } + + public void setRatingValue(Integer ratingValue) { + this.ratingValue = ratingValue; + } + + public Integer getStarRating() { + return starRating; + } + + public void setStarRating(Integer starRating) { + this.starRating = starRating; + } + @Override + public String toString() { + return "{\"article\":\"" + article + "\", \"origin\":\"" + origin + + "\", \"ratingValue\":" + ratingValue + ", \"starRating\":" + starRating+ "}"; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/RouteType.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/RouteType.java new file mode 100644 index 0000000000000000000000000000000000000000..845378aeafb064b8064eea78cd5874b1553903b4 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/RouteType.java @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum RouteType { + bus, + cableCar, + cableTram, + ferry, + funicular, + monorail, + subway, + train, + tram, + trolleybus +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Services.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Services.java new file mode 100644 index 0000000000000000000000000000000000000000..8010673d1e4dabb9b0ae656ad7226e4a3f19de11 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Services.java @@ -0,0 +1,152 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Services { + + @Schema( required = false) + private Boolean defibrillator; + + @Schema( required = false) + private Boolean emergencyPhone; + + @Schema( required = false) + private Boolean informationBoardDevice; + + @Schema( required = false) + private Boolean interactiveDevice; + + @Schema( required = false) + private Boolean messageDevice; + + @Schema( required = false) + private Boolean purchaseDevice; + + @Schema( required = false) + private Boolean restBench; + + @Schema( required = false) + private Boolean shelters; + + @Schema( required = false) + private Boolean timetableDevice; + + @Schema( required = false) + private Boolean voiceDevice; + + @Schema( required = false) + private Boolean wheelChairAccessible; + + public Boolean getDefibrillator() { + return defibrillator; + } + + public void setDefibrillator(Boolean defibrillator) { + this.defibrillator = defibrillator; + } + + public Boolean getEmergencyPhone() { + return emergencyPhone; + } + + public void setEmergencyPhone(Boolean emergencyPhone) { + this.emergencyPhone = emergencyPhone; + } + + public Boolean getInformationBoardDevice() { + return informationBoardDevice; + } + + public void setInformationBoardDevice(Boolean informationBoardDevice) { + this.informationBoardDevice = informationBoardDevice; + } + + public Boolean getInteractiveDevice() { + return interactiveDevice; + } + + public void setInteractiveDevice(Boolean interactiveDevice) { + this.interactiveDevice = interactiveDevice; + } + + public Boolean getMessageDevice() { + return messageDevice; + } + + public void setMessageDevice(Boolean messageDevice) { + this.messageDevice = messageDevice; + } + + public Boolean getPurchaseDevice() { + return purchaseDevice; + } + + public void setPurchaseDevice(Boolean purchaseDevice) { + this.purchaseDevice = purchaseDevice; + } + + public Boolean getRestBench() { + return restBench; + } + + public void setRestBench(Boolean restBench) { + this.restBench = restBench; + } + + public Boolean getShelters() { + return shelters; + } + + public void setShelters(Boolean shelters) { + this.shelters = shelters; + } + + public Boolean getTimetableDevice() { + return timetableDevice; + } + + public void setTimetableDevice(Boolean timetableDevice) { + this.timetableDevice = timetableDevice; + } + + public Boolean getVoiceDevice() { + return voiceDevice; + } + + public void setVoiceDevice(Boolean voiceDevice) { + this.voiceDevice = voiceDevice; + } + + public Boolean getWheelChairAccessible() { + return wheelChairAccessible; + } + + public void setWheelChairAccessible(Boolean wheelChairAccessible) { + this.wheelChairAccessible = wheelChairAccessible; + } + + @Override + public String toString() { + return "{\"defibrillator\":" + defibrillator + ", \"emergencyPhone\":" + emergencyPhone + + ", \"informationBoardDevice\":" + informationBoardDevice + ", \"interactiveDevice\":" + interactiveDevice + + ", \"messageDevice\":" + messageDevice + ", \"purchaseDevice\":" + purchaseDevice + ", \"restBench\":" + restBench + + ", \"shelters\":" + shelters + ", \"timetableDevice\":" + timetableDevice + + ", \"dateMvoiceDeviceodified\":" + voiceDevice + + ", \"wheelChairAccessible\":" + wheelChairAccessible +"}"; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StarRatingDetail.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StarRatingDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..df9f5d3df2aed42a26cff5b248cf6279a3ff3db7 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StarRatingDetail.java @@ -0,0 +1,131 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class StarRatingDetail { + @Schema(description = "", required = false, example = "") + private Double S1; + + @Schema(description = "", required = false, example = "") + private Double S2; + + @Schema(description = "", required = false, example = "") + private Double S3; + + @Schema(description = "", required = false, example = "") + private Double S4; + + @Schema(description = "", required = false, example = "") + private Double S5; + + @Schema(description = "", required = false, example = "") + private Double S6; + + @Schema(description = "", required = false, example = "") + private Double S7; + + @Schema(description = "", required = false, example = "") + private Double S8; + + @Schema(description = "", required = false, example = "") + private Double S9; + + @Schema(description = "", required = false, example = "") + private Double S10; + + public Double getS1() { + return S1; + } + + public void setS1(Double s1) { + S1 = s1; + } + + public Double getS2() { + return S2; + } + + public void setS2(Double s2) { + S2 = s2; + } + + public Double getS3() { + return S3; + } + + public void setS3(Double s3) { + S3 = s3; + } + + public Double getS4() { + return S4; + } + + public void setS4(Double s4) { + S4 = s4; + } + + public Double getS5() { + return S5; + } + + public void setS5(Double s5) { + S5 = s5; + } + + public Double getS6() { + return S6; + } + + public void setS6(Double s6) { + S6 = s6; + } + + public Double getS7() { + return S7; + } + + public void setS7(Double s7) { + S7 = s7; + } + + public Double getS8() { + return S8; + } + + public void setS8(Double s8) { + S8 = s8; + } + + public Double getS9() { + return S9; + } + + public void setS9(Double s9) { + S9 = s9; + } + + public Double getS10() { + return S10; + } + + public void setS10(Double s10) { + S10 = s10; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnected.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnected.java new file mode 100644 index 0000000000000000000000000000000000000000..875528ba529ed8ea3c1c3029db8e2bcfe3f1db59 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnected.java @@ -0,0 +1,134 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class StationConnected { + + @Schema( required = false) + private String architect; + + @Schema( required = false) + private String commissioningDate; + + @Schema( required = false) + private String constructionDate; + + @Schema( required = false) + private List<Currency> currencyAccepted; + + @Schema( required = false) + private List<String> featuredArtist; + + @Schema( required = false) + private StationConnectedItem items; + + @Schema( required = false) + private List<PaymentAccepted> paymentAccepted; + + @Schema( required = false) + private List<Services> services; + + public String getArchitect() { + return architect; + } + + public void setArchitect(String architect) { + this.architect = architect; + } + + public String getCommissioningDate() { + return commissioningDate; + } + + public void setCommissioningDate(String commissioningDate) { + this.commissioningDate = commissioningDate; + } + + public String getConstructionDate() { + return constructionDate; + } + + public void setConstructionDate(String constructionDate) { + this.constructionDate = constructionDate; + } + + public List<Currency> getCurrencyAccepted() { + return currencyAccepted; + } + + public void setCurrencyAccepted(List<Currency> currencyAccepted) { + this.currencyAccepted = currencyAccepted; + } + + public List<String> getFeaturedArtist() { + return featuredArtist; + } + + public void setFeaturedArtist(List<String> featuredArtist) { + this.featuredArtist = featuredArtist; + } + + public StationConnectedItem getItems() { + return items; + } + + public void setItems(StationConnectedItem items) { + this.items = items; + } + + public List<PaymentAccepted> getPaymentAccepted() { + return paymentAccepted; + } + + public void setPaymentAccepted(List<PaymentAccepted> paymentAccepted) { + this.paymentAccepted = paymentAccepted; + } + + public List<Services> getServices() { + return services; + } + + public void setServices(List<Services> services) { + this.services = services; + } + @Override + public String toString() { + String station ="{\"architect\":\"" + architect +"\",\"commissioningDate\":\""+commissioningDate+"\",\"constructionDate\":\""+constructionDate+ + "\", \"currencyAccepted\":["; + for (Currency ca:currencyAccepted) station=station+"\""+ca+"\","; + station = station.substring(0, station.length()-1); + station = station+"], \"featuredArtist\":["; + for (String fa:featuredArtist) station=station+"\""+fa+"\","; + station = station.substring(0, station.length()-1); + station = station+"], \"items\":"+items.toString()+", \"paymentAccepted\":["; + for (PaymentAccepted pa:paymentAccepted) station=station+"\""+pa+"\","; + station = station.substring(0, station.length()-1); + station = station+"], \"services\":["; + for (Services s:services) station=station+""+s.toString()+","; + station = station.substring(0, station.length()-1); + station = station+"]}"; + + + + return station; + + + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnectedItem.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnectedItem.java new file mode 100644 index 0000000000000000000000000000000000000000..41c2ba866f65ad5cb628b8528bf1c4b9fba5fd32 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnectedItem.java @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class StationConnectedItem { + + @Schema( required = false) + private List<String> linesConnected; + + @Schema( required = false) + private StationType stationType; + + public List<String> getLinesConnected() { + return linesConnected; + } + + public void setLinesConnected(List<String> linesConnected) { + this.linesConnected = linesConnected; + } + + public StationType getStationType() { + return stationType; + } + + public void setStationType(StationType stationType) { + this.stationType = stationType; + } + @Override + public String toString() { + String station ="{\"linesConnected\":["; + for (String pt:linesConnected) station=station+"\""+pt+"\","; + station = station.substring(0, linesConnected.size()-1); + station = station+"], \"stationType\":"+stationType+"\"}"; + return station; + + + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationType.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationType.java new file mode 100644 index 0000000000000000000000000000000000000000..ca984a6fe7a3abcc1edd8165ae56310e766e392b --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationType.java @@ -0,0 +1,31 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum StationType { + aerialLift, + bike, + bus, + cableTram, + ferry, + funicular, + monorail, + rail, + subway, + train, + tram, + trolleybus +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/SubCategory.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/SubCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..d4a800efd8a40cd9b0a7bc51ad7fb465d972f162 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/SubCategory.java @@ -0,0 +1,190 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum SubCategory { + museum, + art, + archaeology, + contemporaryArt, + modernArt, + appliedArts, + decorativeArts, + scienceAndTechnology, + fineArts, + music, + sacredArt, + specials, + literature, + medicineAndPharmacy, + maritime, + transports, + military, + wax, + popularArtsAndTraditions, + numismatic, + ceramics, + sumptuaryArts, + naturalScience, + prehistoric, + ethnology, + railway, + mining, + textile, + sculpture, + multiDisciplinar, + painting, + paleonthology, + thematic, + architecture, + museumHouse, + universitary, + bullfighting, + excursion, + sea, + mountain, + river, + countryside, + ancientCity, + cultural, + culinary, + wineRoute, + parksAndGardens, + park, + garden, + fountain, + religiousWorship, + church, + cathedral, + synagogue, + mosque, + buddhistTemple, + hinduTemple, + monastery, + sanctuary, + cemetery, + sumptuar, + history, + castle, + warMemorials, + memorial, + fortifiedCastles, + archaeologicalSite, + crypt, + cave, + shopping, + departmentStore, + luxuryStores, + outlet, + mall, + clothing, + mensClothing, + womensClothing, + childrenClothing, + localProducts, + souvenir, + wine, + pastry, + chocolate, + confectionery, + jewelry, + watch, + shoe, + perfume, + cosmetics, + press, + sport, + optics, + leatherGoods, + decoration, + market, + bike, + book, + computer, + convenience, + electronic, + florist, + furniture, + grocery, + home, + liquor, + mobile, + movierental, + pawnShop, + tire, + toy, + gastronomy, + worldCuisine, + traditional, + provencal, + mediterranean, + greek, + spanish, + brazilian, + lebanese, + creole, + mauritian, + reunion, + hawaiian, + mexican, + american, + texMex, + vegetarian, + fish, + seafood, + indian, + vietnamese, + thai, + laosian, + cambodian, + chinese, + moroccan, + tunisian, + african, + sushi, + japanese, + scandinavian, + russian, + outdoorActivities, + rafting, + canyoning, + aquatichiking, + hiking, + viaferrata, + climbing, + kitesurfing, + canoeing, + paddleboarding, + jetSki, + catamaran, + sailing, + surfing, + deltaPlane, + skiing, + scooter, + karting, + wellness, + spa, + haman, + jacuzzi, + hotSpring, + thalasso, + bodyCare, + swimmingPool, + relaxationArea, + massage, + careCenter +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TouristType.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TouristType.java new file mode 100644 index 0000000000000000000000000000000000000000..9a44df70bd6331511dc777678758346ede1435a2 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TouristType.java @@ -0,0 +1,73 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum TouristType { + ADVENTURETOURISM, + ASTRONOMYTOURISM, + BACKPACKINGTOURISM, + BEACHANDSUNTOURISM, + BEERTOURISM, + BIRDINGTOURISM, + BULLFIGHTINGTOURISM, + BUSINESS, + COMMUNITY_BASEDTOURISM, + CRUISETOURISM, + CULTURALTOURISM, + CYCLINGTOURISM, + DIVINGTOURISM, + ECOTOURISM, + EVENTSANDFESTIVALSTOURISM, + FAMILYTOURISM, + FILMTOURISM, + FISHINGTOURISM, + FOODTOURISM, + GAMBLINGTOURISM, + GEOLOGICALTOURISM, + HERITAGETOURISM, + HUNTINGTOURISM, + INDUSTRIALTOURISM, + LANGUAGETOURISM, + LGTBITOURISM, + LUXURYTOURISM, + MEDICALTOURISM, + MEMORIALTOURISM, + MICETOURISM, + NATURETOURISM, + OLIVEOILTOURISM, + PARTYTOURISM, + PHOTOGRAPHYTOURISM, + RELIGIOUSTOURISM, + ROMANTICTOURISM, + RURALTOURISM, + SAFARITOURISM, + SAILINGTOURISM, + SENIORTOURISM, + SHOPPINGTOURISM, + SHORTBREAKTOURISM, + SINGLESTOURISM, + SPORTSTOURISM, + TOURISM, + TREKKINGTOURISM, + URBANTOURISM, + WATERSPORTSTOURISM, + WEDDING_HONEYMOONTOURISM, + WELLNESSTOURISM, + WHISKYTOURISM, + WINETOURISM, + WINTERSPORTSTOURISM, + WOMENTOURISM +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TransportService.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TransportService.java new file mode 100644 index 0000000000000000000000000000000000000000..ff3ad3aef868362db23984609813b76c67f837aa --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TransportService.java @@ -0,0 +1,22 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum TransportService { + taxi, + uber, + vtc +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripSchedule.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripSchedule.java new file mode 100644 index 0000000000000000000000000000000000000000..6f4c4cbe2d983d1e4bf08207fff9cdfb135f5910 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripSchedule.java @@ -0,0 +1,164 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TripSchedule { + @Schema(description = "", required = false, example = "") + private String byDay; + + @Schema(description = "", required = false, example = "") + private Integer byMonth; + + @Schema(description = "", required = false, example = "") + private Integer byMonthDay; + + @Schema(description = "", required = false, example = "") + private Integer byMonthWeek; + + @Schema(description = "", required = false, example = "") + private DayOfWeek dayOfWeek; + + @Schema(description = "", required = false, example = "") + private Double duration; + + @Schema(description = "", required = false, example = "") + private String endDate; + + @Schema(description = "", required = false, example = "") + private String endTime; + + @Schema(description = "", required = false, example = "") + private String exceptDate; + + @Schema(description = "", required = false, example = "") + private Integer repeatCount; + + @Schema(description = "", required = false, example = "") + private String repeatFrequency; + + @Schema(description = "", required = false, example = "") + private String startDate; + + @Schema(description = "", required = false, example = "") + private String startTime; + + public String getByDay() { + return byDay; + } + + public void setByDay(String byDay) { + this.byDay = byDay; + } + + public Integer getByMonth() { + return byMonth; + } + + public void setByMonth(Integer byMonth) { + this.byMonth = byMonth; + } + + public Integer getByMonthDay() { + return byMonthDay; + } + + public void setByMonthDay(Integer byMonthDay) { + this.byMonthDay = byMonthDay; + } + + public Integer getByMonthWeek() { + return byMonthWeek; + } + + public void setByMonthWeek(Integer byMonthWeek) { + this.byMonthWeek = byMonthWeek; + } + + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + public Double getDuration() { + return duration; + } + + public void setDuration(Double duration) { + this.duration = duration; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public String getExceptDate() { + return exceptDate; + } + + public void setExceptDate(String exceptDate) { + this.exceptDate = exceptDate; + } + + public Integer getRepeatCount() { + return repeatCount; + } + + public void setRepeatCount(Integer repeatCount) { + this.repeatCount = repeatCount; + } + + public String getRepeatFrequency() { + return repeatFrequency; + } + + public void setRepeatFrequency(String repeatFrequency) { + this.repeatFrequency = repeatFrequency; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripStatus.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..12a59ace5bb222f584f78af59466cc6ad5824c05 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripStatus.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum TripStatus { + cancelled, + closed, + finished, + opened, + postponed, + rescheduled, + scheduled, + suspended +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/DataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/DataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..888b0876f484b0ddf6812da840e2985647c7fc30 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/DataModel.java @@ -0,0 +1,233 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.Response; +import com.tecnalia.urbanite.storage.controllers.*; +import org.apache.commons.lang3.StringUtils; +import org.codehaus.jettison.json.JSONObject; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public enum DataModel { + + trafficFlowObserved ("trafficFlowObserved", //id + "Traffic Flow Observed", //name + "An observation of traffic flow conditions at a certain place and time.", //description + "https://github.com/smart-data-models/dataModel.Transportation/tree/master/TrafficFlowObserved", //reference + new TrafficFlowObservedController()) //implementation class + , + daySpecification ("daySpecification", + "Day specification", + "Information about a day: working day, school day, public holiday, day of week.", + "", + new DaySpecificationController()) + , + calendar("calendar", + "Calendar", + "Information about calendars: year, city, days...", + "", + new CalendarController()) + , + airQualityObserved("airQualityObserved", + "Air Quality Observed", + "An observation of air quality conditions at a certain place and time.", + "https://github.com/smart-data-models/dataModel.Environment/tree/master/AirQualityObserved", + new AirQualityObservedController()) + , + nosiseLevelObserved("nosiseLevelObserved", + "Noise level Observed", + "An observation of those acoustic parameters that estimate noise pressure levels at a certain place and time.", + "https://github.com/smart-data-models/dataModel.Environment/tree/master/NoiseLevelObserved", + new NoiseLevelObservedController()) + , + electroMagneticObserved("electroMagneticObserved", + "ElectroMagnetic Observed", + "The Data Model is intended to measure excessive electric and magnetic fields (EMFs), or radiation in a work or public environment according to the level of exposure to electromagnetic fields on the air", + "https://github.com/smart-data-models/dataModel.Environment/tree/master/ElectroMagneticObserved", + new ElectroMagneticObservedController()) + , + weatherObserved ("weatherObserved", + "Weather Observed", + "An observation of weather conditions at a certain place and time.", + "https://github.com/smart-data-models/dataModel.Weather/tree/master/WeatherObserved", + new WeatherObservedController()) + , + event ("event", + "Event", + "Upcoming or past event associated with this place, organization, or action.", + "https://github.com/smart-data-models/dataModel.TourismDestinations/tree/master/Event", + new EventController()) + , + censusObserved ("censusObserved", + "Census Observed", + "Census observation.", + "https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/census-ngsi.jsonld", + new CensusObservedController()) + , + populationObserved ("populationObserved", + "populationObserved", + "populationObserved.", + "https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/population-ngsi.jsonld", + new PopulationObservedController()) + , + pointOfInterest ("pointOfInterest", + "Point of Interest", + "This entity contains a harmonised geographic description of a Point of Interest", + "https://github.com/smart-data-models/dataModel.PointOfInterest/blob/master/PointOfInterest", + new PointOfInterestController()) + , + transportStation ("transportStation", + "TransportStation", + "The data model is a general description of urban stations (Metro, Bus, Tram, Heliport, ...) according to the GFTS standard https://developers.google.com/transit/gtfs/reference/#stopstxt, as well the detailed description of these (means of access, platform, assistance, ...).", + "https://github.com/smart-data-models/dataModel.Transportation/tree/master/TransportStation", + new TransportStationController()) + , + gtfsShape ("gtfsShape", + "GtfsShape", + "GtfsShape", + "https://github.com/smart-data-models/dataModel.UrbanMobility/blob/master/GtfsShape", + new GtfsShapeController()) + , + touristTrip ("touristTrip", + "TouristTrip", + "A tourist trip. A created itinerary of visits to one or more places of interest (TouristAttraction/TouristDestination) often linked by a similar theme, geographic area, or interest to a particular touristType. The UNWTO defines tourism trip as the Trip taken by visitors.", + "https://github.com/smart-data-models/dataModel.TourismDestinations/blob/master/TouristTrip", + new TouristTripController()) + , + + originDestinationMatrix ("originDestinationMatrix", + "Origin Destination Matrix", + "Origin Destination Matrix.", + "https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/ODMatrix-ngsi.jsonld", + new OriginDestinationMatrixController()) + , + + mapLayer ("mapLayer", + "GeoJson Storage", + "GeoJson Storage", + "https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/maplayer-ngsi.jsonld", + new MapLayerController()) + , + vehicle ("vehicle", + "Vehicle", + "This entity models a particular vehicle model, including all properties which are common to multiple vehicle instances belonging to such model.", + "https://github.com/smart-data-models/dataModel.UrbanMobility/blob/master/GtfsShape", + new VehicleController()) + , + /* + * Rest of models here + */ + ; + + private String id; + private String name; + private String description; + private String reference; + private IGenericController iOps; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getReference() { + return reference; + } + public void setReference(String reference) { + this.reference = reference; + } + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + private DataModel(String id, String name, String description, String reference, IGenericController iOps) { + this.iOps = iOps; + this.id = id; + this.name = name; + this.description = description; + this.reference = reference; + } + + public APIResponse insertData(City city, String data) { + return iOps.insertData(city, data); + } + + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + return iOps.getTDataRange(city, startDate, endDate, filters, returnFields, limit, sort); + } + + public APIResponse updateData(City city, String id, String data) { + return iOps.updateData(city, id, data); + } + + public APIResponse getDataByID(City city, String id) { + return iOps.getDataByID(city, id); + } + + public APIResponse getTData (City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + return iOps.getTData (city, filters, returnFields, limit, sort); + } + public JSONObject getExample() { + return iOps.getExample(); + } + public APIResponse getDistinct(City city, String field) { + return iOps.getDistinct(city, field); + } + public APIResponse getDistinct(City city, String[] field) { + return iOps.getDistinct(city, field); + } + public APIResponse deleteDataByID(City city, String id) { + return iOps.deleteDataByID(city, id); + } + + public Response aggregate (City city, String metric, Date startDate, Date endDate, AggregatorEnum aggregatorEnum, String downsample, Map<String,String> tags) + { + String compoundMetric = (this.getId() + "." + metric).toLowerCase(); + return new AggregatorController().aggregate(city, compoundMetric, startDate, endDate, aggregatorEnum, downsample, tags); + } + + public static Boolean any(String id) + { + if(!StringUtils.isBlank(id)) + { + for(DataModel dataModel : DataModel.values()) + { + if(id.equals(dataModel.id)) + { + return true; + } + } + } + return false; + } + + + +} + diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/AirQualityObserved.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/AirQualityObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..276202d29ff5be6426c7b0745bf859b8c7c297d0 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/AirQualityObserved.java @@ -0,0 +1,654 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.TypeOfLocation; +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class AirQualityObserved { + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "Air quality index is a number used to report the quality of the air on any given day.", required = false) + private Integer airQualityIndex; + + @Schema(description = "Overall qualitative level of health concern corresponding to the air quality observed.", required = false) + private String airQualityLevel; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "Arsenic detected.", required = false) + private Double as; + + @Schema(description = "Benzene detected.", required = false) + private Double c6h6; + + @Schema(description = "Cadmium detected.", required = false) + private Double cd; + + @Schema(description = "Carbon Monoxide detected.", required = false) + private Double co; + + @Schema(description = "Carbon Dioxide detected.", required = false) + private Double co2; + + @Schema(description = "Qualitative Carbon Monoxide presence.", required = false) + private String coLevel; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTCformat.", required = true) + private String dateObserved; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "Nickel detected.", required = false) + private Double ni; + + @Schema(description = "Nitrogen monoxide detected.", required = false) + private Double no; + + @Schema(description = "Nitrogen dioxide detected.", required = false) + private Double no2; + + @Schema(description = "Other Nitrogen oxides detected.", required = false) + private Double nox; + + @Schema(description = "Ozone detected.", required = false) + private Double o3; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "Lead detected.", required = false) + private Double pb; + + @Schema(description = "Particulate matter 10 micrometers or less in diameter.", required = false) + private Double pm10; + + @Schema(description = "Particulate matter 2.5 micrometers or less in diameter.", required = false) + private Double pm25; + + @Schema(description = "Amount of water rain.", required = false) + private Double precipitation; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "A reference to a point of interest (usually an air quality station) associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "A reference to the weather observed associated to the air quality conditions described by this entity.", required = false) + private String refWeatherObserved; + + @Schema(description = "Humidity in the Air.", required = false) + private Double relativeHumidity; + + @Schema(description = "Reliability (percentage, expressed in parts per one) corresponding to the air quality observed.", required = false) + private Double reliability; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "Hydrogen sulfide detected.", required = false) + private Double sh2; + + @Schema(description = "Sulfur dioxide detected.", required = false) + private Double so2; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "'Temperature of the item.", required = false) + private Double temperature; + + @Schema(description = "NGSI Entity type. Must be 'AirQualityObserved'.", required = true) + private String type; + + @Schema(description = "Type of location of the sampled item.", required = false) + private TypeOfLocation typeofLocation; + + @Schema(description = "Alkanes <C10, ketones <C6, aldehydes <C10, carboxylic acids <C5, aspirits<C7, Alkenes <C8, Aromatics.", required = false) + private Double volatileOrganicCompoundsTotal; + + @Schema(description = "Direction of the weather vane.", required = false) + private Double windDirection; + + @Schema(description = "Intensity of the wind.", required = false) + private Double windSpeed; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public Integer getAirQualityIndex() { + return airQualityIndex; + } + + public void setAirQualityIndex(Integer airQualityIndex) { + this.airQualityIndex = airQualityIndex; + } + + public String getAirQualityLevel() { + return airQualityLevel; + } + + public void setAirQualityLevel(String airQualityLevel) { + this.airQualityLevel = airQualityLevel; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public Double getAs() { + return as; + } + + public void setAs(Double as) { + this.as = as; + } + + public Double getC6h6() { + return c6h6; + } + + public void setC6h6(Double c6h6) { + this.c6h6 = c6h6; + } + + public Double getCd() { + return cd; + } + + public void setCd(Double cd) { + this.cd = cd; + } + + public Double getCo() { + return co; + } + + public void setCo(Double co) { + this.co = co; + } + + public Double getCo2() { + return co2; + } + + public void setCo2(Double co2) { + this.co2 = co2; + } + + public String getCoLevel() { + return coLevel; + } + + public void setCoLevel(String coLevel) { + this.coLevel = coLevel; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getNi() { + return ni; + } + + public void setNi(Double ni) { + this.ni = ni; + } + + public Double getNo() { + return no; + } + + public void setNo(Double no) { + this.no = no; + } + + public Double getNo2() { + return no2; + } + + public void setNo2(Double no2) { + this.no2 = no2; + } + + public Double getNox() { + return nox; + } + + public void setNox(Double nox) { + this.nox = nox; + } + + public Double getO3() { + return o3; + } + + public void setO3(Double o3) { + this.o3 = o3; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public Double getPb() { + return pb; + } + + public void setPb(Double pb) { + this.pb = pb; + } + + public Double getPm10() { + return pm10; + } + + public void setPm10(Double pm10) { + this.pm10 = pm10; + } + + public Double getPm25() { + return pm25; + } + + public void setPm25(Double pm25) { + this.pm25 = pm25; + } + + public Double getPrecipitation() { + return precipitation; + } + + public void setPrecipitation(Double precipitation) { + this.precipitation = precipitation; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public String getRefWeatherObserved() { + return refWeatherObserved; + } + + public void setRefWeatherObserved(String refWeatherObserved) { + this.refWeatherObserved = refWeatherObserved; + } + + public Double getRelativeHumidity() { + return relativeHumidity; + } + + public void setRelativeHumidity(Double relativeHumidity) { + this.relativeHumidity = relativeHumidity; + } + + public Double getReliability() { + return reliability; + } + + public void setReliability(Double reliability) { + this.reliability = reliability; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public Double getSh2() { + return sh2; + } + + public void setSh2(Double sh2) { + this.sh2 = sh2; + } + + public Double getSo2() { + return so2; + } + + public void setSo2(Double so2) { + this.so2 = so2; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public TypeOfLocation getTypeofLocation() { + return typeofLocation; + } + + public void setTypeofLocation(TypeOfLocation typeofLocation) { + this.typeofLocation = typeofLocation; + } + + public Double getVolatileOrganicCompoundsTotal() { + return volatileOrganicCompoundsTotal; + } + + public void setVolatileOrganicCompoundsTotal(Double volatileOrganicCompoundsTotal) { + this.volatileOrganicCompoundsTotal = volatileOrganicCompoundsTotal; + } + + public Double getWindDirection() { + return windDirection; + } + + public void setWindDirection(Double windDirection) { + this.windDirection = windDirection; + } + + public Double getWindSpeed() { + return windSpeed; + } + + public void setWindSpeed(Double windSpeed) { + this.windSpeed = windSpeed; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved, location + if (id == null || type == null || dateObserved == null || location == null) return false; + + //DateObserved: valid date + if (Utils.ISO2Date(dateObserved) == null) return false; + + //type: must be "AirQualityObserved"; + if (type.compareTo("AirQualityObserved") != 0) return false; + + //Minimum/maximum values + if (airQualityIndex != null && airQualityIndex < 0) return false; + if (as != null && as < 0) return false; + if (c6h6 != null && c6h6 < 0) return false; + if (cd != null && cd < 0) return false; + if (co != null && co < 0) return false; + if (co2 != null && co2 < 0) return false; + if (ni != null && ni < 0) return false; + if (no != null && no < 0) return false; + if (no2 != null && no2 < 0) return false; + if (nox != null && nox < 0) return false; + if (o3 != null && o3 < 0) return false; + if (pb != null && pb < 0) return false; + if (pm10 != null && pm10 < 0) return false; + if (pm25 != null && pm25 < 0) return false; + if (precipitation != null && precipitation < 0) return false; + if (relativeHumidity!= null && (relativeHumidity < 0 || relativeHumidity > 1)) return false; + if (reliability!= null && (reliability < 0 || reliability > 1)) return false; + if (sh2 != null && sh2 < 0) return false; + if (so2 != null && so2 < 0) return false; + if (volatileOrganicCompoundsTotal != null && volatileOrganicCompoundsTotal < 0) return false; + if (windDirection!= null && (windDirection < -180 || windDirection > 180)) return false; + if (windSpeed != null && windSpeed < 0) return false; + if (airQualityLevel != null && airQualityLevel.length() < 2) return false; + + //all ok + return true; + } + + public static AirQualityObserved createAirQualityObserved(String data) { + + AirQualityObserved aq = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + aq = Utils.JSON2Object(obj.toString(), AirQualityObserved.class); + aq.setLocation(pol); + aq.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to AirQualityObserved: " + e.getMessage()); + } + + return aq; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"airQualityIndex\":\"" + airQualityIndex + + "\", \"airQualityLevel\":\"" + airQualityLevel + "\", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\", \"as\":\"" + as + "\", \"c6h6\":\"" + c6h6 + + "\", \"cd\":\"" + cd + "\", \"co\":\"" + co + "\", \"co2\":\"" + co2 + "\", \"coLevel\":\"" + coLevel + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"ni\":\"" + ni + "\", \"no\":\"" + no + "\", \"no2\":\"" + no2 + + "\", \"nox\":\"" + nox + "\", \"o3\":\"" + o3 + "\", \"owner\":\"" + owner + "\", \"pb\":\"" + pb + + "\", \"pm10\":\"" + pm10 + "\", \"pm25\":\"" + pm25 + "\", \"precipitation\":\"" + precipitation + + "\", \"refDevice\":\"" + refDevice + "\", \"refPointOfInterest\":\"" + refPointOfInterest + + "\", \"refWeatherObserved\":\"" + refWeatherObserved + "\", \"relativeHumidity\":\"" + + relativeHumidity + "\", \"reliability\":\"" + reliability + "\", \"seeAlso\":\"" + seeAlso + + "\", \"sh2\":\"" + sh2 + "\", \"so2\":\"" + so2 + "\", \"source\":\"" + source + + "\", \"temperature\":\"" + temperature + "\", \"type\":\"" + type + "\", \"typeofLocation\":\"" + + typeofLocation + "\", \"volatileOrganicCompoundsTotal\":\"" + volatileOrganicCompoundsTotal + + "\", \"windDirection\":\"" + windDirection + "\", \"windSpeed\":\"" + windSpeed + "\", \"context\":\"" + + context + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/ElectroMagneticObserved.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/ElectroMagneticObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..00fa6b66b2e7254d2f1d924560c03ee134f122dc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/ElectroMagneticObserved.java @@ -0,0 +1,373 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ElectroMagneticObserved { + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTCformat.", required = true) + private String dateObserved; + + @Schema(description = "Observation period start date and time.", required = false) + private String dateObservedFrom; + + @Schema(description = "Observation period end date and time.", required = false) + private String dateObservedTo; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Level corresponding to the observed survey. The unit code (text) is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **MHz** represents Mega Hertz.", required = true) + private String EMF; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "A reference to a point of interest (usually an air quality station) associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "Percent for confidence Factor. The unit code (text) is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **P1** represents Percent. ", required = false) + private Double reliability; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'ElectroMagneticObserved'.", required = true) + private String type; + + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateObservedFrom() { + return dateObservedFrom; + } + + public void setDateObservedFrom(String dateObservedFrom) { + this.dateObservedFrom = dateObservedFrom; + } + + public String getDateObservedTo() { + return dateObservedTo; + } + + public void setDateObservedTo(String dateObservedTo) { + this.dateObservedTo = dateObservedTo; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getEMF() { + return EMF; + } + + public void setEMF(String eMF) { + EMF = eMF; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public Double getReliability() { + return reliability; + } + + public void setReliability(Double reliability) { + this.reliability = reliability; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + + if (id == null || type == null || dateObserved == null || location == null) return false; + + if (Utils.ISO2Date(dateObserved) == null) return false; + + if (type.compareTo("ElectroMagneticObserved") != 0) return false; + + //Minimum/maximum values + + + //all ok + return true; + } + + public static ElectroMagneticObserved createElectroMagneticObserved(String data) { + + ElectroMagneticObserved aq = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + aq = Utils.JSON2Object(obj.toString(), ElectroMagneticObserved.class); + aq.setLocation(pol); + aq.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to ElectroMagneticObserved: " + e.getMessage()); + } + + return aq; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\"" + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\"" + + "\", \"EMF\":\"" + EMF + "\", \"reliability\":\"" + reliability + + "\", \"refDevice\":\"" + refDevice + "\", \"refPointOfInterest\":\"" + refPointOfInterest + + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + + "\", \"type\":\"" + type + "\", \"typeofLocation\":\"" + + "\", \"context\":\"" + + context + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/EnvironmentDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/EnvironmentDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..0c08267aac340ab4d4daea3ddbd24fa6dd33c748 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/EnvironmentDataModel.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +public enum EnvironmentDataModel { + AIRQUALITYOBSERVED, + NOISELEVELOBSERVED, + ELECTROMAGNETICOBSERVED + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/NoiseLevelObserved.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/NoiseLevelObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..c5bc448077993760e44ef20a2dac7e8b55314408 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/NoiseLevelObserved.java @@ -0,0 +1,435 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.TypeOfLocation; +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class NoiseLevelObserved { + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTCformat.", required = false) + private String dateObserved; + + @Schema(description = "Observation period start date and time.", required = true) + private String dateObservedFrom; + + @Schema(description = "Observation period end date and time.", required = true) + private String dateObservedTo; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "A reference to a point of interest (usually an air quality station) associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "A reference to the weather observed associated to the air quality conditions described by this entity.", required = false) + private String refWeatherObserved; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "Class of sonometer (0, 1, 2) according to ANSI used for taking this observation.", required = false) + private String sonometerClass; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'NoiseLevelObserved'.", required = true) + private String type; + + @Schema(description = "frequencies", required = false) + private HashMap<String, Double> frequencies; + + @Schema(description = "LAmax", required = false) + private Double LAmax; + + @Schema(description = "LAeq", required = false) + private Double LAeq; + + @Schema(description = "LAeq_d", required = false) + private Double LAeq_d; + + @Schema(description = "LAS'.", required = false) + private Double LAS; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateObservedFrom() { + return dateObservedFrom; + } + + public void setDateObservedFrom(String dateObservedFrom) { + this.dateObservedFrom = dateObservedFrom; + } + + public String getDateObservedTo() { + return dateObservedTo; + } + + public void setDateObservedTo(String dateObservedTo) { + this.dateObservedTo = dateObservedTo; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public String getRefWeatherObserved() { + return refWeatherObserved; + } + + public void setRefWeatherObserved(String refWeatherObserved) { + this.refWeatherObserved = refWeatherObserved; + } + + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSonometerClass() { + return sonometerClass; + } + + public void setSonometerClass(String sonometerClass) { + this.sonometerClass = sonometerClass; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + public Double getLAmax() { + return LAmax; + } + + public void setLAmax(Double lAmax) { + LAmax = lAmax; + } + + public Double getLAeq() { + return LAeq; + } + + public void setLAeq(Double lAeq) { + LAeq = lAeq; + } + + public Double getLAeq_d() { + return LAeq_d; + } + + public void setLAeq_d(Double lAeq_d) { + LAeq_d = lAeq_d; + } + + public Double getLAS() { + return LAS; + } + + public void setLAS(Double lAS) { + LAS = lAS; + } + + public HashMap<String, Double> getFrequencies() { + return frequencies; + } + + public void setFrequencies(HashMap<String, Double> frequencies) { + this.frequencies = frequencies; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved, location + if (id == null || type == null || dateObservedFrom == null || dateObservedTo == null || location == null) return false; + + //DateObserved: valid date + //if (Utils.ISO2Date(dateObserved) == null) return false; + if (Utils.ISO2Date(dateObservedFrom) == null) return false; + if (Utils.ISO2Date(dateObservedTo) == null) return false; + + + //type: must be "NoiseLevelObserved"; + if (type.compareTo("NoiseLevelObserved") != 0) return false; + + //Minimum/maximum values + + + //all ok + return true; + } + + public static NoiseLevelObserved createNoiseLevelObserved(String data) { + + NoiseLevelObserved aq = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + aq = Utils.JSON2Object(obj.toString(), NoiseLevelObserved.class); + aq.setLocation(pol); + aq.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to AirQualityObserved: " + e.getMessage()); + } + + return aq; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\"" + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\"" + + "\", \"refDevice\":\"" + refDevice + "\", \"refPointOfInterest\":\"" + refPointOfInterest + + "\", \"refWeatherObserved\":\"" + refWeatherObserved + "\", \"relativeHumidity\":\"" + + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + + "\", \"type\":\"" + type + "\", \"typeofLocation\":\"" + + "\", \"context\":\"" + + context + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/Event.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/Event.java new file mode 100644 index 0000000000000000000000000000000000000000..407ce8e40374ab43155a820d88ed4050cd2966ca --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/Event.java @@ -0,0 +1,329 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Event; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Event { + + @Schema(description = "The category of the event.", required = false) + private String category; + + @Schema(description = "The championship of the match.", required = false) + private String championship; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The end date and time of the event (in ISO 8601 date format).", required = false) + private String endDate; + + @Schema(description = "The guest team of the match.", required = false) + private String guestTeam; + + @Schema(description = "The home team of the match.", required = false) + private String homeTeam; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the event. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = false) + private LocationGeojson location; + + @Schema(description = "The start date and time of the event (in ISO 8601 date format).", required = false) + private String startDate; + + @Schema(description = "NGSI Entity type. Must be 'Event'.", required = true) + private String type; + + @Schema(description = "Number of passangers of the ship.", required = false) + private int passengerCount; + + @Schema(description = "Harbour of departure of the ship.", required = false) + private String departsFromHarbour; + + @Schema(description = "Harbour of arrival of the ship.", required = false) + private String arrivesToHarbour; + + @Schema(description = "Ship identifier or name.", required = false) + private String ship; + + @Schema(description = "Terminal of the trip of the ship.", required = false) + private String terminal; + + @Schema(description = "Type of the route.", required = false) + private String routeType; + + @Schema(description = "Sub-category of the category attribute.", required = false) + private String subCategory; + + @Schema(hidden = true) + private List<String> context; + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getChampionship() { + return championship; + } + + public void setChampionship(String championship) { + this.championship = championship; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getGuestTeam() { + return guestTeam; + } + + public void setGuestTeam(String guestTeam) { + this.guestTeam = guestTeam; + } + + public String getHomeTeam() { + return homeTeam; + } + + public void setHomeTeam(String homeTeam) { + this.homeTeam = homeTeam; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public int getPassengerCount() { + return passengerCount; + } + + public void setPassengerCount(int passengerCount) { + this.passengerCount = passengerCount; + } + + public String getDepartsFromHarbour() { + return departsFromHarbour; + } + + public void setDepartsFromHarbour(String departsFromHarbour) { + this.departsFromHarbour = departsFromHarbour; + } + + public String getArrivesToHarbour() { + return arrivesToHarbour; + } + + public void setArrivesToHarbour(String arrivesToHarbour) { + this.arrivesToHarbour = arrivesToHarbour; + } + + public String getShip() { + return ship; + } + + public void setShip(String ship) { + this.ship = ship; + } + + public String getTerminal() { + return terminal; + } + + public void setTerminal(String terminal) { + this.terminal = terminal; + } + + public String getRouteType() { + return routeType; + } + + public void setRouteType(String routeType) { + this.routeType = routeType; + } + + public String getSubCategory() { + return subCategory; + } + + public void setSubCategory(String subCategory) { + this.subCategory = subCategory; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type + if (id == null || type == null) return false; + + //startDate and endDate: valid dates (if present) + if (startDate != null && Utils.ISO2Date(startDate) == null) return false; + if (endDate != null && Utils.ISO2Date(endDate) == null) return false; + + //type: must be "Event"; + if (type.compareTo("Event") != 0) return false; + + //all ok + return true; + } + + public static Event createEvent(String data) { + + Event ev = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + ev = Utils.JSON2Object(obj.toString(), Event.class); + ev.setLocation(pol); + ev.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to Event: " + e.getMessage()); + } + + return ev; + } + + @Override + public String toString() { + return "{\"category\":\"" + category + "\", \"championship\":\"" + championship + "\", \"dateCreated\":\"" + + dateCreated + "\", \"dateModified\":\"" + dateModified + "\", \"endDate\":\"" + endDate + + "\", \"guestTeam\":\"" + guestTeam + "\", \"homeTeam\":\"" + homeTeam + "\", \"id\":\"" + id + + "\", \"location\":\"" + location + "\", \"startDate\":\"" + startDate + "\", \"type\":\"" + type + + "\", \"context\":\"" + context + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/EventDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/EventDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..1e5d185a22dbe3227d46b466d05f25fa3c343ae6 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/EventDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Event; + +public enum EventDataModel { + EVENT +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GeometryType.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GeometryType.java new file mode 100644 index 0000000000000000000000000000000000000000..6dc8978aae9a98db106bacfce9c2913b5358aff5 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GeometryType.java @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum GeometryType { + Point, + MultiPoint, + LineString, + MultiLineString, + Polygon, + MultiPolygon +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShape.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShape.java new file mode 100644 index 0000000000000000000000000000000000000000..45620ed34137eb698cd84598051f9c7c3905f8b2 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShape.java @@ -0,0 +1,282 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.GtfsShape; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class GtfsShape { + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTC format. ", required = false) + private String dateObserved; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "The geographical shape associated to this entity encoded as GeoJSON LineString or MultiLineString.", required = true) + private LocationGeojson location; + + @Schema(description = "An array of the distance travelled when reaching each of the points that make the LineString or MultiLineString that represents this shape. It shall match the same number of elements as the corresponding LineString or MultiLineString.", required = false) + private List<Integer> distanceTravelled; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'GtfsShape'.", required = true) + private String type; + + @Schema(hidden = true) + private List<String> context; + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public List<Integer> getDistanceTravelled() { + return distanceTravelled; + } + + public void setDistanceTravelled(List<Integer> distanceTravelled) { + this.distanceTravelled = distanceTravelled; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id , type and location + if (id == null || type == null || location == null ) return false; + + if (dateObserved != null) { + if (Utils.ISO2Date(dateObserved) == null) + return false; + } + //type: must be "GtfsShape"; + if (type.compareTo("GtfsShape") != 0) return false; + + + return true; + } + public static GtfsShape createGtfsShape(String data) { + + GtfsShape gshape = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + gshape = Utils.JSON2Object(obj.toString(), GtfsShape.class); + gshape.setLocation(pol); + gshape.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to GtfsShape: " + e.getMessage()); + } + + return gshape; + } + @Override + public String toString() { + return "{\"alternateName\":\"" + alternateName + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"distanceTravelled\":\"" + distanceTravelled + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"owner\":\"" + owner + + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + "\", \"type\":\"" + type + "\", \"context\":\"" + + context + "\"}"; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShapedataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShapedataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..f84c53e37c4598d009fc1a5a034f01a3f16c8c14 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShapedataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.GtfsShape; + +public enum GtfsShapedataModel { + GTFSSHAPE +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayer.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayer.java new file mode 100644 index 0000000000000000000000000000000000000000..012ce3bdc5aa2f2ee0a1d1ecb6ec70ca153e8834 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayer.java @@ -0,0 +1,193 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Map; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + + +import io.swagger.v3.oas.annotations.media.Schema; + +public class MapLayer { + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "Indicates the specific type of the map for visualization mechanism ", required = false) + private String subtype; + + @Schema(description = "NGSI Entity type. Must be 'MapLayer'.", required = true) + private String type; + + @Schema(description = "GeoJson content", required = true) + private JSONObject map ; + + + @Schema(hidden = true) + private List<String> context; + + + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public JSONObject getMap() { + return map; + } + + public void setMap(JSONObject map) { + this.map = map; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + @Schema(hidden = true) + public boolean isValid() { + + + if (id == null || type == null || getMap() == null ) return false; + + + if (type.compareTo("MapLayer") != 0) return false; + + + return true; + } + + public static MapLayer createMapLayer(String data) { + + MapLayer aq = new MapLayer();; + + try { + List<String> cnt = null; + JSONObject map = null; + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + if (obj.has("map")) { + map = obj.getJSONObject("map"); + + } + if (obj.has("type")) { + aq.setType(obj.getString("type")); + } + if (obj.has("name")) { + aq.setName(obj.getString("name")); + } + if (obj.has("subtype")) { + aq.setSubtype(obj.getString("subtype")); + } + if (obj.has("alternateName")) { + aq.setAlternateName(obj.getString("alternateName")); + } + if (obj.has("description")) { + aq.setDescription(obj.getString("description")); + } + if (obj.has("id")) { + aq.setId(obj.getString("id")); + } + aq.setMap( map); + //aq = Utils.JSON2Object(obj.toString(), MapLayer.class); + aq.setContext(cnt); + + } catch (Exception e) { + + System.out.println(" Exception converting JSON [" + data + "] to MapLayer: " + e.getMessage()); + return null; + } + + return aq; + } + + @Override + public String toString() { + return "{}"; + } + + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayerDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayerDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..22a5fc97cf0e2bea0f57b52b4755c49b22601006 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayerDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Map; + +public enum MapLayerDataModel { + MAPLAYER +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterest.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterest.java new file mode 100644 index 0000000000000000000000000000000000000000..09e5454a372cb7e2e5e8d41e81854ca48ef13ab2 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterest.java @@ -0,0 +1,365 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.PointOfInterest; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.ContactPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PointOfInterest { + + @Schema(description = "URL from which additional information of the subject can be obtained", required = false) + private String additionalInfoURL; + + @Schema(description = "The mailing address", required = false) + private Address address; + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided", required = false) + private String areaServed; + + @Schema(description = "Category of this point of interest. Allowed values: Those defined by the [Factual taxonomy](https://github.com/Factual/places/blob/master/categories/factual_taxonomy.json) together with the extended categories described by the specification. For instance the value `113` corresponds to beaches, and the value `311` corresponds to museums.", required = false) + private String category; + + @Schema(description = "The details to contact with the item.", required = false) + private ContactPoint contactPoint; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "The name of this item.", required = false) + private List<String> refSeeAlso; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'PointOfInterest'.", required = true) + private String type; + + @Schema(description = "Ward ID of the entity corresponding to this observation.", required = false) + private String wardId; + + @Schema(description = "Zone ID of the entity corresponding to this observation.", required = false) + private String zoneId; + + @Schema(description = "Zone name of the entity corresponding to this observation.", required = false) + private String zoneName; + + @Schema(hidden = true) + private List<String> context; + + public String getAdditionalInfoURL() { + return additionalInfoURL; + } + + public void setAdditionalInfoURL(String additionalInfoURL) { + this.additionalInfoURL = additionalInfoURL; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public ContactPoint getContactPoint() { + return contactPoint; + } + + public void setContactPoint(ContactPoint contactPoint) { + this.contactPoint = contactPoint; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public List<String> getRefSeeAlso() { + return refSeeAlso; + } + + public void setRefSeeAlso(List<String> refSeeAlso) { + this.refSeeAlso = refSeeAlso; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getWardId() { + return wardId; + } + + public void setWardId(String wardId) { + this.wardId = wardId; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getZoneName() { + return zoneName; + } + + public void setZoneName(String zoneName) { + this.zoneName = zoneName; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id and type + if (id == null || type == null || name == null || category == null) return false; + + + //type: must be "TrafficFlowObserved"; + if (type.compareTo("PointOfInterest") != 0) return false; + + + return true; + } + public static PointOfInterest createPointOfInterest(String data) { + + PointOfInterest poi = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + poi = Utils.JSON2Object(obj.toString(), PointOfInterest.class); + poi.setLocation(pol); + poi.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to PointOfInterest: " + e.getMessage()); + } + + return poi; + } + @Override + public String toString() { + return "{\"additionalInfoURL\":\"" + additionalInfoURL + "\", \"address\":\"" + address + + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + areaServed + + "\", \"category\":\"" + category + "\", \"contactPoint\":\"" + contactPoint.toString() + "\", \"dataProvider\":\"" + dataProvider + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"owner\":\"" + owner + + "\", \"refSeeAlso\":\"" + refSeeAlso + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + "\", \"type\":\"" + type + "\", \"wardId\":\"" + + wardId + "\", \"zoneId\":\"" + zoneId + + "\", \"zoneName\":\"" + zoneName + "\"context\":\"" + + context + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterestDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterestDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..8a9ba6790a3084f968cb816f3fd62ae50fd01b7f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterestDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.PointOfInterest; + +public enum PointOfInterestDataModel { + POINTOFINTEREST +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/CensusObserved.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/CensusObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..91d9d8b4ab669dcb5f510b0c1c85b2dd4b8644cc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/CensusObserved.java @@ -0,0 +1,415 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Population; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class CensusObserved { + //Description: Census Observed + //REF: https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/census-ngsi.jsonld + + @Schema(description = "addressRegion.", required = false) + private String addressRegion; + + @Schema(description = "age.", required = false) + private Integer age; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String createdAt; + + @Schema(description = "currentEconomicStatus.", required = false) + private Integer currentEconomicStatus; + + @Schema(description = "The date and time of this observation in ISO8601 UTC format.", required = true) + private String dateObserved; + + @Schema(description = "disabilityBenefits.", required = false) + private Double disabilityBenefits; + + @Schema(description = "educationAllowances.", required = false) + private Double educationAllowances; + + @Schema(description = "employeeCashIncome.", required = false) + private Double employeeCashIncome; + + @Schema(description = "gender.", required = false) + private String gender; + + @Schema(description = "householdCSWeight.", required = false) + private Double householdCSWeight; + + @Schema(description = "householdID.", required = false) + private Integer householdID; + + @Schema(description = "hsize.", required = false) + private Integer hsize; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "GeoJSON Geometry.", required = false) + private LocationGeojson location; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String modifiedAt; + + @Schema(description = "nationality.", required = false) + private String nationality; + + @Schema(description = "netIncome.", required = false) + private Double netIncome; + + @Schema(description = "oldAgeBenefits.", required = false) + private Double oldAgeBenefits; + + @Schema(description = "personalCSWeight.", required = false) + private Double personalCSWeight; + + @Schema(description = "selfEmploymentLosses.", required = false) + private Double selfEmploymentLosses; + + @Schema(description = "sicknessBenefits.", required = false) + private Double sicknessBenefits; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "survivorBenefits.", required = false) + private Double survivorBenefits; + + @Schema(description = "NGSI Entity type. It has to be 'CensusFlowObserved'.", required = true) + private String type; + + @Schema(description = "unemploymentBenefits.", required = false) + private Double unemploymentBenefits; + + @Schema(hidden = true) + private List<String> context; + + public String getAddressRegion() { + return addressRegion; + } + + public void setAddressRegion(String addressRegion) { + this.addressRegion = addressRegion; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public Integer getCurrentEconomicStatus() { + return currentEconomicStatus; + } + + public void setCurrentEconomicStatus(Integer currentEconomicStatus) { + this.currentEconomicStatus = currentEconomicStatus; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public Double getDisabilityBenefits() { + return disabilityBenefits; + } + + public void setDisabilityBenefits(Double disabilityBenefits) { + this.disabilityBenefits = disabilityBenefits; + } + + public Double getEducationAllowances() { + return educationAllowances; + } + + public void setEducationAllowances(Double educationAllowances) { + this.educationAllowances = educationAllowances; + } + + public Double getEmployeeCashIncome() { + return employeeCashIncome; + } + + public void setEmployeeCashIncome(Double employeeCashIncome) { + this.employeeCashIncome = employeeCashIncome; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public Double getHouseholdCSWeight() { + return householdCSWeight; + } + + public void setHouseholdCSWeight(Double householdCSWeight) { + this.householdCSWeight = householdCSWeight; + } + + public Integer getHouseholdID() { + return householdID; + } + + public void setHouseholdID(Integer householdID) { + this.householdID = householdID; + } + + public Integer getHsize() { + return hsize; + } + + public void setHsize(Integer hsize) { + this.hsize = hsize; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getModifiedAt() { + return modifiedAt; + } + + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + + public String getNationality() { + return nationality; + } + + public void setNationality(String nationality) { + this.nationality = nationality; + } + + public Double getNetIncome() { + return netIncome; + } + + public void setNetIncome(Double netIncome) { + this.netIncome = netIncome; + } + + public Double getOldAgeBenefits() { + return oldAgeBenefits; + } + + public void setOldAgeBenefits(Double oldAgeBenefits) { + this.oldAgeBenefits = oldAgeBenefits; + } + + public Double getPersonalCSWeight() { + return personalCSWeight; + } + + public void setPersonalCSWeight(Double personalCSWeight) { + this.personalCSWeight = personalCSWeight; + } + + public Double getSelfEmploymentLosses() { + return selfEmploymentLosses; + } + + public void setSelfEmploymentLosses(Double selfEmploymentLosses) { + this.selfEmploymentLosses = selfEmploymentLosses; + } + + public Double getSicknessBenefits() { + return sicknessBenefits; + } + + public void setSicknessBenefits(Double sicknessBenefits) { + this.sicknessBenefits = sicknessBenefits; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Double getSurvivorBenefits() { + return survivorBenefits; + } + + public void setSurvivorBenefits(Double survivorBenefits) { + this.survivorBenefits = survivorBenefits; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Double getUnemploymentBenefits() { + return unemploymentBenefits; + } + + public void setUnemploymentBenefits(Double unemploymentBenefits) { + this.unemploymentBenefits = unemploymentBenefits; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved + if (id == null || type == null || dateObserved == null) return false; + + //DateObserved: valid date + if (Utils.ISO2Date(dateObserved) == null) return false; + + //type: must be "CensusObserved"; + if (type.compareTo("CensusObserved") != 0) return false; + + //more checks? + + return true; + } + + public static CensusObserved createCensusObserved(String data) { + + CensusObserved census = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + census = Utils.JSON2Object(obj.toString(), CensusObserved.class); + census.setLocation(pol); + census.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to CensusObserved: " + e.getMessage()); + } + + return census; + } + + + @Override + public String toString() { + return "{\"addressRegion\":\"" + addressRegion + "\", \"age\":\"" + age + "\", \"createdAt\":\"" + createdAt + + "\", \"currentEconomicStatus\":\"" + currentEconomicStatus + "\", \"dateObserved\":\"" + dateObserved + + "\", \"disabilityBenefits\":\"" + disabilityBenefits + "\", \"educationAllowances\":\"" + + educationAllowances + "\", \"employeeCashIncome\":\"" + employeeCashIncome + "\", \"gender\":\"" + + gender + "\", \"householdCSWeight\":\"" + householdCSWeight + "\", \"householdID\":\"" + householdID + + "\", \"hsize\":\"" + hsize + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"modifiedAt\":\"" + modifiedAt + "\", \"nationality\":\"" + nationality + "\", \"netIncome\":\"" + + netIncome + "\", \"oldAgeBenefits\":\"" + oldAgeBenefits + "\", \"personalCSWeight\":\"" + + personalCSWeight + "\", \"selfEmploymentLosses\":\"" + selfEmploymentLosses + + "\", \"sicknessBenefits\":\"" + sicknessBenefits + "\", \"source\":\"" + source + + "\", \"survivorBenefits\":\"" + survivorBenefits + "\", \"type\":\"" + type + + "\", \"unemploymentBenefits\":\"" + unemploymentBenefits + "\", \"context\":\"" + context + "\"}"; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..10f0e0495077bcf61de5bcd1ee2500ff9db0c75d --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationDataModel.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Population; + +public enum PopulationDataModel { + CENSUSOBSERVED, + POPULATIONOBSERVED +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationObserved.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..1c6ef08bf5e7293dacc44d0d81d5842d198c91ed --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationObserved.java @@ -0,0 +1,214 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Population; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.google.gson.FieldNamingPolicy; +import com.tecnalia.urbanite.storage.DataModel.Common.District; +import com.tecnalia.urbanite.storage.DataModel.Common.DoubleValue; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.MaleFemale; +import com.tecnalia.urbanite.storage.DataModel.Common.PopulationSummary; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PopulationObserved { + + + + @Schema( description = "Unique identifier of the entity.", required = true) + private String id; + + + @Schema(description = "NGSI Entity type. It has to be 'CensusFlowObserved'.", required = true) + private String type; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A description of this item.", required = false) + private String description; + + + @Schema(description = "The date and time of this observation in ISO8601 UTC format.", required = false) + private String dateObserved; + + @Schema(description = "populationSummary", name = "population-summary", required = false) + private PopulationSummary populationSummary; + + @Schema(description = "populationAges", name = "population-ages", required = false) + private HashMap<String, MaleFemale> populationAges; + + @Schema(description = "districtsSummary", name = "districts-summary", required = false) + private HashMap<String, District> districtsSummary; + + @Schema(description = "educationPercentages", name = "education-percentages", required = false) + private HashMap<String, DoubleValue> educationPercentages; + + @Schema(description = "workingPeople", name = "working-people", required = false) + private HashMap<String, DoubleValue> workingPeople; + + + @Schema(hidden = true) + private List<String> context; + + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PopulationSummary getPopulationSummary() { + return populationSummary; + } + + public void setPopulationSummary(PopulationSummary populationSummary) { + this.populationSummary = populationSummary; + } + + public HashMap<String, MaleFemale> getPopulationAges() { + return populationAges; + } + + public void setPopulationAges(HashMap<String, MaleFemale> populationAges) { + this.populationAges = populationAges; + } + + public HashMap<String, District> getDistrictsSummary() { + return districtsSummary; + } + + public void setDistrictsSummary(HashMap<String, District> districtsSummary) { + this.districtsSummary = districtsSummary; + } + + public HashMap<String, DoubleValue> getEducationPercentages() { + return educationPercentages; + } + + public void setEducationPercentages(HashMap<String, DoubleValue> educationPercentages) { + this.educationPercentages = educationPercentages; + } + + public HashMap<String, DoubleValue> getWorkingPeople() { + return workingPeople; + } + + public void setWorkingPeople(HashMap<String, DoubleValue> workingPeople) { + this.workingPeople = workingPeople; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type + if (id == null || type == null) return false; + + //DateObserved: valid date + if (dateObserved != null) + if (Utils.ISO2Date(dateObserved) == null) return false; + + + if (type.compareTo("PopulationObserved") != 0) return false; + + //more checks? + + return true; + } + + public static PopulationObserved createPopulationObserved(String data) { + + PopulationObserved census = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + census = Utils.JSON2Object(obj.toString(), PopulationObserved.class, FieldNamingPolicy.LOWER_CASE_WITH_DASHES); + + census.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to PopulationObserved: " + e.getMessage()); + } + + return census; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/SortingMode.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/SortingMode.java new file mode 100644 index 0000000000000000000000000000000000000000..5610abe785a2f67a04f68d7c5cd8a453ff122803 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/SortingMode.java @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum SortingMode { + ASC (1), + DESC (-1); + + private final int value; + + private SortingMode(int value) { + this.value = value; + } + + public int getOrder() { + return value; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/Calendar.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/Calendar.java new file mode 100644 index 0000000000000000000000000000000000000000..005a10b92e7b2b62a0ea7a92c3b6169e3badfdcc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/Calendar.java @@ -0,0 +1,193 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Time; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.EnumUtils; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class Calendar { + + private String id = null; + private String type = null; + private String city = null; + private LocationGeojson location; + private Integer year = null; + private List<String> days = new ArrayList<String>(); + private List<String> context = new ArrayList<String>(); + + private String createdAt; //from database + private String modifiedAt; //from database + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getCity() { + return city; + } + public void setCity(String city) { + this.city = city; + } + public LocationGeojson getLocation() { + return location; + } + public void setLocation(LocationGeojson location) { + this.location = location; + } + public Integer getYear() { + return year; + } + public void setYear(Integer year) { + this.year = year; + } + public List<String> getDays() { + return days; + } + public void setDays(List<String> days) { + this.days = days; + } + + public String getCreatedAt() { + return createdAt; + } + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + public String getModifiedAt() { + return modifiedAt; + } + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + + public List<String> getContext() { + return context; + } + public void setContext(List<String> context) { + this.context = context; + } + + public static Calendar createCalendar (String data) { + + try { + + JSONObject obj= new JSONObject(data); + + //location + LocationGeojson pol = null; + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + + + Calendar cal = Utils.JSON2Object(obj.toString(), Calendar.class); //without "location" + if (cal != null) + cal.setLocation(pol); + + //@context --> context + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + for (int i = 0; i < arrCont.length(); i++) { + cal.context.add(arrCont.getString(i)); + } + } + + + return cal; + + } catch (JSONException e) { + return null; + } + } + + public boolean isValid() { + + //mandatory fields + if (this.id == null + || this.type == null + || this.year == null + || days.isEmpty()) + return false; + + //type must be "DaySpecification" + if (type.compareTo("Calendar") != 0) return false; + + //city + if (this.city != null && EnumUtils.isValidEnum(City.class, city.toLowerCase()) == false) return false; + + //year + if (this.year <= 0) return false; + + return true; + } + + + + @Override + public String toString() { + return "{\"id\":\"" + id + "\", \"type\":\"" + type + "\", \"city\":\"" + city + "\", \"location\":\"" + + location + "\", \"year\":\"" + year + "\", \"days\":\"" + days + "\", \"@context\":\"" + context + + "\", \"createdAt\":\"" + createdAt + "\", \"modifiedAt\":\"" + modifiedAt + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/DaySpecification.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/DaySpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..843aebdf5bc2d89b2d8e15199ee34f7a2684632f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/DaySpecification.java @@ -0,0 +1,174 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Time; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.Utils.Utils; + + +public class DaySpecification { + + //@Schema(description = "Unique identifier", required = true, example = "urn:ngsi-ld:DaySpecification:Bilbao:2021_01_01") + private String id = null; + private String type = null; + private String date = null; + private String description = null; + private Integer workingDay = null; + private Integer schoolDay = null; + private Integer publicHoliday = null; + private Integer weekDay = null; + private List<String> context = new ArrayList<String>(); + + private String createdAt; //from database + private String modifiedAt; //from database + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getDate() { + return date; + } + public void setDate(String date) { + this.date = date; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public int getWorkingDay() { + return workingDay; + } + public void setWorkingDay(int workingDay) { + this.workingDay = workingDay; + } + public int getSchoolDay() { + return schoolDay; + } + public void setSchoolDay(int schoolDay) { + this.schoolDay = schoolDay; + } + public int getPublicHoliday() { + return publicHoliday; + } + public void setPublicHoliday(int publicHoliday) { + this.publicHoliday = publicHoliday; + } + public int getWeekDay() { + return weekDay; + } + public void setWeekDay(int weekDay) { + this.weekDay = weekDay; + } + public String getCreatedAt() { + return createdAt; + } + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + public String getModifiedAt() { + return modifiedAt; + } + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + public List<String> getContext() { + return context; + } + public void setContext(List<String> context) { + this.context = context; + } + + public static DaySpecification createDaySpecification(String data) { + + try { + + DaySpecification day = Utils.JSON2Object(data, DaySpecification.class); + + //@context --> context + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + for (int i = 0; i < arrCont.length(); i++) { + day.context.add(arrCont.getString(i)); + } + } + + return day; + } catch (JSONException e) { + return null; + } + } + + public boolean isValid() { + + //mandatory fields + if (this.id == null + || this.type == null + || this.date == null + || this.workingDay == null + || this.schoolDay == null + || this.publicHoliday == null + || this.weekDay == null) + return false; + + //type must be "DaySpecification" + if (type.compareTo("DaySpecification") != 0) return false; + + //valid date + try { + String[] sDate = this.date.split("-"); + Calendar cal = Calendar.getInstance(); + cal.setLenient(false); + cal.set(Integer.parseInt(sDate[0]), Integer.parseInt(sDate[1]) - 1 /*January: month 0*/, Integer.parseInt(sDate[2])); + Date d = cal.getTime(); //if it's not a valid date --> exception + } catch (Exception e) { + return false; + } + + //TODO: more ckecks: values 0 or 1 for workingDay, schoolDay, publicHoliday, and 1 to 7 for weekDay. + return true; + } + + @Override + public String toString() { + return "{\"id\":\"" + id + "\", \"type\":\"" + type + "\", \"date\":\"" + date + "\", \"description\":\"" + + description + "\", \"workingDay\":\"" + workingDay + "\", \"schoolDay\":\"" + schoolDay + + "\", \"publicHoliday\":\"" + publicHoliday + "\", \"weekDay\":\"" + weekDay + "\", \"createdAt\":\"" + + createdAt + "\", \"modifiedAt\":\"" + modifiedAt + "\", \"@context\":\"" + context + "\"}"; + } + + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/TimeDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/TimeDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..44f435b30207276ddde76f96cbe4685d45be9c6e --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/TimeDataModel.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Time; + +public enum TimeDataModel { + DAYSPECIFICATION, + CALENDAR +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTrip.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTrip.java new file mode 100644 index 0000000000000000000000000000000000000000..cf1411041c680fba2a81822fbf4318fe8813d8aa --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTrip.java @@ -0,0 +1,742 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TouristTrip; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.Audience; +import com.tecnalia.urbanite.storage.DataModel.Common.Category; +import com.tecnalia.urbanite.storage.DataModel.Common.CriticReview; +import com.tecnalia.urbanite.storage.DataModel.Common.Currency; +import com.tecnalia.urbanite.storage.DataModel.Common.ElectricTransport; +import com.tecnalia.urbanite.storage.DataModel.Common.Itinerary; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.OpeningHoursSpecification; +import com.tecnalia.urbanite.storage.DataModel.Common.PaymentAccepted; +import com.tecnalia.urbanite.storage.DataModel.Common.Pitch; +import com.tecnalia.urbanite.storage.DataModel.Common.PriceSpecification; +import com.tecnalia.urbanite.storage.DataModel.Common.RouteType; +import com.tecnalia.urbanite.storage.DataModel.Common.StarRatingDetail; +import com.tecnalia.urbanite.storage.DataModel.Common.SubCategory; +import com.tecnalia.urbanite.storage.DataModel.Common.TouristType; +import com.tecnalia.urbanite.storage.DataModel.Common.TransportService; +import com.tecnalia.urbanite.storage.DataModel.Common.TripSchedule; +import com.tecnalia.urbanite.storage.DataModel.Common.TripStatus; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TouristTrip { + @Schema(description = "Text or Link to the access plan to the Trip.", required = false) + private String accessPlan; + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "Type of public concerned by this Trip. A combination of Free text (family, adult, children, teenager, senior, allPublic, ...). Enum:''adult, allPublic, children, family, senior, teenager''", required = false) + private Audience audience; + + @Schema(description = "Category of the Trip. A combination of free text to remain flexible to a specific context is offered below as an initial repository or any other value needed by an application. enum:''excursion, gastronomy, history, museum, outdoorActivities, parksAndGardens, religiousWorship, shopping, wellness''", required = false) + private Category category; + + @Schema(description = "Specifies the URL to the official image or video of the Trip for more information.", required = false) + private String contentURL; + + @Schema(description = "Specifies the URL to the official image or video of the Trip for more information.", required = false) + private CriticReview criticReview; + + @Schema(description = "Currency accepted for payment if `TripFree` is False. A combination of a list of active codes defined in the model. [Norme ISO 4217](http://en.wikipedia.org/wiki/ISO_4217), [Crypto Currencies](https://en.wikipedia.org/wiki/List_of_cryptocurrencies) , [Exchange Trading System](https://en.wikipedia.org/wiki/Local_exchange_trading_system)", required = false) + private List<Currency> currencyAccepted; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Last official update of the data in ISO 8601 format", required = false) + private String dateLastReported; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "The duration of each show. The unit code (text) of measurement is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **HUR** represents **Hours**.", required = false) + private Double duration; + + @Schema(description = "List of the different types of electric transport proposed by the city. A combination of. Enum:''electricBicycle, electricCar, electricMotorBike, electricScooter''", required = false) + private List<ElectricTransport> electricTransport; + + @Schema(description = "End date and time in an ISO8601 UTC format", required = false) + private String endDate; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Free or paid Trip (True = Free / False = Paid).", required = false) + private Boolean isAccessibleForFree; + + @Schema(description = "Destinations or places that make up a trip. For a trip where destination order is important use ItemList to specify that order included in the trips.", required = false) + private List<Itinerary> itinerary; + + @Schema(description = "List of Formal language used during the Trip expressed from the IETF [BCP 47](https://tools.ietf.org/html/bcp47) standard", required = false) + private List<String> language; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon' ", required = true) + private LocationGeojson location; + + @Schema(description = "Name of the trip location.", required = false) + private String locationName; + + @Schema(description = "The total number of people who can attend to the Trip at that location.", required = false) + private Integer maximumAttendeeCapacity; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A structured value providing information about the opening hours of a place or a certain service inside a place", required = false) + private List<OpeningHoursSpecification> openingHoursSpecification; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "Accepted payment if `TripFree` is False. A combination of a list of active codes defined in the model. Enum:''Cash, CreditCard, CryptoCurrency, other''", required = false) + private List<PaymentAccepted> paymentAccepted; + + @Schema(description = "Pitch of the Trip. Each items have the format based on the [Internationalization (i18N) - W3C recommandation for multilanguage](https://www.w3.org/TR/json-ld/#string-internationalization) integrating all items in a single property (ex number 71). Each item is represented by a string with Language Value : Article Value. ", required = false) + private List<Pitch> pitch; + + @Schema(description = "A structured value representing a price or price range depending categories or public.", required = false) + private List<PriceSpecification> priceSpecification; + + @Schema(description = "Rating value of Trips. Usage guidelines: Use values from 0 to 10 depending on your standard. this is the average value of all detailed scores of `starRatingDetailed` attribute", required = false) + private Double ratingValueAverage; + + @Schema(description = "Reference to all the Point Of interest [PointOfInterest](https://github.com/smart-data-models/dataModel.PointOfInterest/blob/master/PointOfInterest/doc/spec.md) included in the trips. The POI list does not have a chronological order.", required = false) + private List<String> refPointOfInterest; + + @Schema(description = "List of the urban transports (subway, Bus, Tram, ...) available near the Trip according to the GFTS standard [STOP](https://developers.google.com/transit/gtfs/reference/#stopstxt). A combination of values. Enum:' bus, cableCar, cableTram, ferry, funicular, monorail, subway, train, tram, trolleybus'", required = false) + private List<RouteType> routeType; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "Trip header line, matches the text hook.", required = false) + private String slogan; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "Detailed star ratings which led to the average value expressed in the ratingValue. Instructions for use: A structured value from 1 to 10 occurrences (Stars) where each element is a string in the format: `NumberOfSTar`: Percent. ", required = false) + private StarRatingDetail starRatingDetailed; + + @Schema(description = "Start date and time in an ISO8601 UTC format", required = false) + private String startDate; + + @Schema(description = "Sub-category of the `category` attribute. A combination of free text to remain flexible to a specific context is offered below as an initial example or any other value needed by an application. ", required = false) + private SubCategory subCategory; + + @Schema(description = "Reference to a list of Minor Trips that are part of this major Trip", required = false) + private String subTrip; + + @Schema(description = "Property. Identifier format of any NGSI entity", required = false) + private String superTrip; + + @Schema(description = "A list of thematic as keywords", required = false) + private List<String> thematic; + + @Schema(description = "Title of the Trip.", required = false) + private String title; + + @Schema(description = "enumeration of different tourist types applicable to the TouristTrip", required = false) + private List<TouristType> touristType; + + @Schema(description = "List of private transport available near the Trip. In example taxi, uber, vtc, parkingShuttle", required = false) + private List<TransportService>transportServices; + + @Schema(description = "Min Price. The unit code (text) of measurement is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **EUR** represents **€uro**.", required = false) + private Double tripPriceFrom; + + @Schema(description = "Max Price. The unit code (text) of measurement is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **EUR** represents **€uro**.", required = false) + private Double tripPriceTo; + + @Schema(description = "Trip Schedule. This allows a schedule to be set over a repeated period of time used to describe an Trip that occurs regularly. In example nota in the beginning of the section for restriction to use this attribute.", required = false) + private List<TripSchedule> tripSchedule; + + @Schema(description = "Trip Status regarding this Trip. Enum:''cancelled, closed, finished, opened, postponed, rescheduled, scheduled, suspended''", required = true) + private TripStatus tripStatus; + + @Schema(description = "NGSI Entity type. Must be 'TouristDestination'.", required = true) + private String type; + + @Schema(description = "Link to the official website for more information.", required = true) + private String webSite; + + @Schema(description = "Access possible for Person with Reduced Mobility.", required = true) + private Boolean wheelChairAccessible; + + @Schema(description = "Custom field for Urbanite, id of the trip", required = true) + private String idTrip; + + @Schema(description = "Custom field for Urbanite, customer of the trip", required = true) + private String customer; + + + @Schema(hidden = true) + private List<String> context; + + public String getAccessPlan() { + return accessPlan; + } + + public void setAccessPlan(String accessPlan) { + this.accessPlan = accessPlan; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public Audience getAudience() { + return audience; + } + + public void setAudience(Audience audience) { + this.audience = audience; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public String getContentURL() { + return contentURL; + } + + public void setContentURL(String contentURL) { + this.contentURL = contentURL; + } + + public CriticReview getCriticReview() { + return criticReview; + } + + public void setCriticReview(CriticReview criticReview) { + this.criticReview = criticReview; + } + + public List<Currency> getCurrencyAccepted() { + return currencyAccepted; + } + + public void setCurrencyAccepted(List<Currency> currencyAccepted) { + this.currencyAccepted = currencyAccepted; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateLastReported() { + return dateLastReported; + } + + public void setDateLastReported(String dateLastReported) { + this.dateLastReported = dateLastReported; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Double getDuration() { + return duration; + } + + public void setDuration(Double duration) { + this.duration = duration; + } + + public List<ElectricTransport> getElectricTransport() { + return electricTransport; + } + + public void setElectricTransport(List<ElectricTransport> electricTransport) { + this.electricTransport = electricTransport; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Boolean getIsAccessibleForFree() { + return isAccessibleForFree; + } + + public void setIsAccessibleForFree(Boolean isAccessibleForFree) { + this.isAccessibleForFree = isAccessibleForFree; + } + + public List<Itinerary> getItinerary() { + return itinerary; + } + + public void setItinerary(List<Itinerary> itinerary) { + this.itinerary = itinerary; + } + + public List<String> getLanguage() { + return language; + } + + public void setLanguage(List<String> language) { + this.language = language; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getLocationName() { + return locationName; + } + + public void setLocationName(String locationName) { + this.locationName = locationName; + } + + public Integer getMaximumAttendeeCapacity() { + return maximumAttendeeCapacity; + } + + public void setMaximumAttendeeCapacity(Integer maximumAttendeeCapacity) { + this.maximumAttendeeCapacity = maximumAttendeeCapacity; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<OpeningHoursSpecification> getOpeningHoursSpecification() { + return openingHoursSpecification; + } + + public void setOpeningHoursSpecification(List<OpeningHoursSpecification> openingHoursSpecification) { + this.openingHoursSpecification = openingHoursSpecification; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public List<PaymentAccepted> getPaymentAccepted() { + return paymentAccepted; + } + + public void setPaymentAccepted(List<PaymentAccepted> paymentAccepted) { + this.paymentAccepted = paymentAccepted; + } + + public List<Pitch> getPitch() { + return pitch; + } + + public void setPitch(List<Pitch> pitch) { + this.pitch = pitch; + } + + public List<PriceSpecification> getPriceSpecification() { + return priceSpecification; + } + + public void setPriceSpecification(List<PriceSpecification> priceSpecification) { + this.priceSpecification = priceSpecification; + } + + public Double getRatingValueAverage() { + return ratingValueAverage; + } + + public void setRatingValueAverage(Double ratingValueAverage) { + this.ratingValueAverage = ratingValueAverage; + } + + public List<String> getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(List<String> refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public List<RouteType> getRouteType() { + return routeType; + } + + public void setRouteType(List<RouteType> routeType) { + this.routeType = routeType; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSlogan() { + return slogan; + } + + public void setSlogan(String slogan) { + this.slogan = slogan; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public StarRatingDetail getStarRatingDetailed() { + return starRatingDetailed; + } + + public void setStarRatingDetailed(StarRatingDetail starRatingDetailed) { + this.starRatingDetailed = starRatingDetailed; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public SubCategory getSubCategory() { + return subCategory; + } + + public void setSubCategory(SubCategory subCategory) { + this.subCategory = subCategory; + } + + public String getSubTrip() { + return subTrip; + } + + public void setSubTrip(String subTrip) { + this.subTrip = subTrip; + } + + public String getSuperTrip() { + return superTrip; + } + + public void setSuperTrip(String superTrip) { + this.superTrip = superTrip; + } + + public List<String> getThematic() { + return thematic; + } + + public void setThematic(List<String> thematic) { + this.thematic = thematic; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List<TouristType> getTouristType() { + return touristType; + } + + public void setTouristType(List<TouristType> touristType) { + this.touristType = touristType; + } + + public List<TransportService> getTransportServices() { + return transportServices; + } + + public void setTransportServices(List<TransportService> transportServices) { + this.transportServices = transportServices; + } + + public Double getTripPriceFrom() { + return tripPriceFrom; + } + + public void setTripPriceFrom(Double tripPriceFrom) { + this.tripPriceFrom = tripPriceFrom; + } + + public Double getTripPriceTo() { + return tripPriceTo; + } + + public void setTripPriceTo(Double tripPriceTo) { + this.tripPriceTo = tripPriceTo; + } + + public List<TripSchedule> getTripSchedule() { + return tripSchedule; + } + + public void setTripSchedule(List<TripSchedule> tripSchedule) { + this.tripSchedule = tripSchedule; + } + + public TripStatus getTripStatus() { + return tripStatus; + } + + public void setTripStatus(TripStatus tripStatus) { + this.tripStatus = tripStatus; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getWebSite() { + return webSite; + } + + public void setWebSite(String webSite) { + this.webSite = webSite; + } + + public Boolean getWheelChairAccessible() { + return wheelChairAccessible; + } + + public void setWheelChairAccessible(Boolean wheelChairAccessible) { + this.wheelChairAccessible = wheelChairAccessible; + } + + public String getIdTrip() { + return idTrip; + } + + public void setIdTrip(String idTrip) { + this.idTrip = idTrip; + } + + public String getCustomer() { + return customer; + } + + public void setCustomer(String customer) { + this.customer = customer; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public static TouristTrip createTouristTrip(String data) { + + TouristTrip tt = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + tt = Utils.JSON2Object(obj.toString(), TouristTrip.class); + tt.setLocation(pol); + tt.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to TouristTrip: " + e.getMessage()); + } + + return tt; + } + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id and type + if (id == null || type == null) return false; + + //Required dateObserved or dateObservedFrom + dateObservedTo + + + return true; + } + + @Override + public String toString() { + return "{\"webSite\":\"" + webSite + "\", \"address\":\"" + address + + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + areaServed + + "\", \"category\":\"" + category + "\", \"wheelChairAccessible\":\"" + wheelChairAccessible + "\", \"dataProvider\":\"" + dataProvider + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"owner\":\"" + owner + + "\", \"tripStatus\":\"" + tripStatus + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + "\", \"type\":\"" + type + "\", \"tripPriceFrom\":\"" + + tripPriceFrom + "\", \"tripPriceTo\":\"" + tripPriceTo + + "\", \"transportServices\":\"" + transportServices + "\"context\":\"" + + context + "\"}"; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTripDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTripDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..a555892c3156369ca25c52c02e606d5cf933ab59 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTripDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TouristTrip; + +public enum TouristTripDataModel { + TouristTrip +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStation.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStation.java new file mode 100644 index 0000000000000000000000000000000000000000..27a264c70c77bc2612b58ff618ed8e956ffdec52 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStation.java @@ -0,0 +1,512 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TransportStation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.ContactPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.Dimension; +import com.tecnalia.urbanite.storage.DataModel.Common.InstallationMode; +import com.tecnalia.urbanite.storage.DataModel.Common.Inventory; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.OpeningHoursSpecification; +import com.tecnalia.urbanite.storage.DataModel.Common.StationConnected; +import com.tecnalia.urbanite.storage.DataModel.Common.StationType; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TransportStation { + @Schema(description = "The mailing address", required = false) + private Address address; + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided", required = false) + private String areaServed; + + @Schema(description = "The details to contact with the item.", required = false) + private ContactPoint contactPoint; + + @Schema(description = "Name of the contracting authority.", required = false) + private String contractingAuthority; + + @Schema(description = "Name of the contracting company responsible for the exploitation of the station.", required = false) + private String contractingCompany; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "A timestamp which denotes the last time when the device successfully reported data. Date and time in an ISO8601 UTCformat.", required = true) + private String dateLastReported; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "Global dimension. The format is structured by a sub-property of 3 items. The unit code (text) is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **MTR** represents Meters", required = false) + private Dimension dimension; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Location relative to the ground reference. Enum:''aerial, ground, underGround, underSea''", required = false) + private InstallationMode installationMode; + + @Schema(description = "General data mapping only for `locationType` = 0, 1, 3, 4. The format is structured by a sub-property of 4 items.", required = false) + private Inventory inventory; + + @Schema(description = "Floor on which the location is located. Numerical index associated with the floor. Indicates the relative position of this stage in relation to the others. The index 0 indicates the ground floor. The floors above ground level are indicated by positive indices, and the underground stages by negative indices.", required = false) + private Integer levelId; + + @Schema(description = "The geographical shape associated to this entity encoded as GeoJSON LineString or MultiLineString.", required = false) + private LocationGeojson location; + + @Schema(description = "Link to the GTFS standard repository describing the different location [Location Type]. 0 Stop or platform (place where users get on or off in a public transport vehicle). 1 Station (area or physical structure comprising one or more platforms). 2 Entrance or Exit (place where users can enter / exit a station from the street). 3 Generic intersection (location in a station that doesn''t correspond to any other `location_type` value). 4 Boarding area of a specific location on a platform where users can get on / off in a vehicle.", required = false) + private String locationType; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A structured value providing information about the opening hours of a place or a certain service inside a place", required = false) + private List<OpeningHoursSpecification> openingHoursSpecification; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "Link to the GTFS standard repository describing the different link between Station and Platform [Parent STATION]. Case ''1'' location_type = 0 (Stop / platform ), the parent_station field contains the ID of a station. Case ''2'' location_type = 1 (Station), this field must be empty. Case ''3'' location_type = 2 (Input / output) or location_type = 3 (generic intersection), the parent_station field contains the ID of a station location_type = 1. Case ''4'' location_type = 4 (boarding area), the parent_station field contains the ID of a platform.", required = false) + private String parentStation; + + @Schema(description = "Platform identifier for a platform type stop `location_type` = 0 when the stop is in a station.", required = false) + private Integer platformCode; + + @Schema(description = "A reference to a point of interest associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "Connections possible from this station. A structured value from 0 to N occurrences where each items is a string in the format `stationType` : [List of Lines connected, separated by a comma]. Enum:''aerialLift, bus, cableTram, ferry, funicular, monorail, rail, subway, train, tram, trolleybus''", required = false) + private List<StationConnected> stationConnected; + + @Schema(description = "Type of transport station. Enum:''aerialLift, bus, cableTram, ferry, funicular, monorail, rail, subway, trolleybus, tram''", required = false) + private List<StationType> stationType; + + @Schema(description = "NGSI Entity type. Must be 'TransportStation'.", required = true) + private String type; + + @Schema(description = "Link to the official website for more information.", required = false) + private String webSite; + + @Schema(description = "Access possible for Person with Reduced Mobility. For stops without parents 0 no information is available regarding the accessibility of the stop. 1 some vehicles at this stop can board a PMR user. 2 PRM user cannot board at this stop. For a stop that is part of a station 0 the stop inherits the wheelchair_boarding behavior of the parent station, if it is filled in. 1 lanes provide wheelchair access to the stop / platform from outside the station. 2 no lane provides wheelchair access to the stop / platform from outside the station. For station inputs / outputs 0 the station entry inherits the wheelchair_boarding behavior of the main station, if specified. 1 the station entrance is wheelchair accessible. 2 no wheelchair accessible route connects the station entrance to the stops / platforms.", required = false) + private Integer wheelChairAccessible; + + + @Schema(description = "Pricing zone of the station.", required = true) + private String zoneId; + + @Schema(hidden = true) + private List<String> context; + + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public ContactPoint getContactPoint() { + return contactPoint; + } + + public void setContactPoint(ContactPoint contactPoint) { + this.contactPoint = contactPoint; + } + + public String getContractingAuthority() { + return contractingAuthority; + } + + public void setContractingAuthority(String contractingAuthority) { + this.contractingAuthority = contractingAuthority; + } + + public String getContractingCompany() { + return contractingCompany; + } + + public void setContractingCompany(String contractingCompany) { + this.contractingCompany = contractingCompany; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateLastReported() { + return dateLastReported; + } + + public void setDateLastReported(String dateLastReported) { + this.dateLastReported = dateLastReported; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Dimension getDimension() { + return dimension; + } + + public void setDimension(Dimension dimension) { + this.dimension = dimension; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public InstallationMode getInstallationMode() { + return installationMode; + } + + public void setInstallationMode(InstallationMode installationMode) { + this.installationMode = installationMode; + } + + public Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + public Integer getLevelId() { + return levelId; + } + + public void setLevelId(Integer levelId) { + this.levelId = levelId; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getLocationType() { + return locationType; + } + + public void setLocationType(String locationType) { + this.locationType = locationType; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<OpeningHoursSpecification> getOpeningHoursSpecification() { + return openingHoursSpecification; + } + + public void setOpeningHoursSpecification(List<OpeningHoursSpecification> openingHoursSpecification) { + this.openingHoursSpecification = openingHoursSpecification; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getParentStation() { + return parentStation; + } + + public void setParentStation(String parentStation) { + this.parentStation = parentStation; + } + + public Integer getPlatformCode() { + return platformCode; + } + + public void setPlatformCode(Integer platformCode) { + this.platformCode = platformCode; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public List<StationConnected> getStationConnected() { + return stationConnected; + } + + public void setStationConnected(List<StationConnected> stationConnected) { + this.stationConnected = stationConnected; + } + + public List<StationType> getStationType() { + return stationType; + } + + public void setStationType(List<StationType> stationType) { + this.stationType = stationType; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getWebSite() { + return webSite; + } + + public void setWebSite(String webSite) { + this.webSite = webSite; + } + + public Integer getWheelChairAccessible() { + return wheelChairAccessible; + } + + public void setWheelChairAccessible(Integer wheelChairAccessible) { + this.wheelChairAccessible = wheelChairAccessible; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + public static TransportStation createTransportStation(String data) throws Exception { + + TransportStation ts = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + ts = Utils.JSON2Object(obj.toString(), TransportStation.class); + ts.setLocation(pol); + ts.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to TransportStation: " + e.getMessage()); + throw e; + } + + return ts; + } + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id and type + if (id == null || type == null) return false; + + if (!type.equals("TransportStation")) return false; + + + return true; + } + @Override + public String toString() { + + String retorno= "{\"address\":" + address.toString() + ", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\", \"contactPoint\":" + contactPoint.toString() + + ", \"contractingAuthority\":\"" + contractingAuthority + "\", \"contractingCompany\":\"" + contractingCompany + "\", \"dataProvider\":\"" + dataProvider + + "\", \"dateCreated\":\"" + dateCreated + "\", \"dateLastReported\":\"" + dateLastReported + + "\", \"dateModified\":\"" + dateModified + + "\", \"description\":\"" + description + "\", \"dimension\":" + dimension.toString() + ", \"id\":\"" + id + + "\", \"installationMode\":\"" + installationMode + "\", \"inventory\":" + inventory.toString() + + ", \"levelId\":" + levelId + " \"location\":" + location.toString() + + ", \"locationType\":\"" + locationType + "\", \"name\":\"" + name + + ", \"openingHoursSpecification\":["; + for (OpeningHoursSpecification pt:openingHoursSpecification) retorno=retorno+""+pt.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"owner\":["; + + for (String o:owner) retorno=retorno+""+o.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"parentStation\":\"" + parentStation + + "\", \"platformCode\":\"" + platformCode + "\", \"refPointOfInterest\":" + refPointOfInterest + + "\", \"seeAlso\":["; + for (String o:seeAlso) retorno=retorno+""+o.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"source\":\"" + source + + "\", \"stationConnected\":["; + for (StationConnected pt:stationConnected) retorno=retorno+""+pt.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"stationType\":["; + + for (StationType pt:stationType) retorno=retorno+""+pt.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"type\":\"" + type + + + "\", \"webSite\":\"" + webSite + "\", \"wheelChairAccessible\":" + wheelChairAccessible.toString() + +",\"zoneId\":"+zoneId+"\"}"; + return retorno; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStationDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStationDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..33a657ea01abeaab35f21dfe2837c78f9ca78d52 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStationDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TransportStation; + +public enum TransportStationDataModel { + TransportStation +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/LaneDirection.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/LaneDirection.java new file mode 100644 index 0000000000000000000000000000000000000000..6840d593df94786fb8cc4b83a828e4f73ae7d18b --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/LaneDirection.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +public enum LaneDirection { + forward, + backward +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/ODMatrixValue.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/ODMatrixValue.java new file mode 100644 index 0000000000000000000000000000000000000000..1a53c55cf32eda99b399750cdc277928cd2c8dcc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/ODMatrixValue.java @@ -0,0 +1,86 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ODMatrixValue { + + + @Schema(description = ".", required = false) + private String arrivesTo; + + @Schema(description = ".", required = false) + private String departsFrom; + + @Schema(description = ".", required = false) + private Integer time; + + @Schema(description = ".", required = false) + private Double volume; + + @Schema(description = ".", required = false) + private Double volumePercentage; + + public String getArrivesTo() { + return arrivesTo; + } + + public void setArrivesTo(String arrivesTo) { + this.arrivesTo = arrivesTo; + } + + public String getDepartsFrom() { + return departsFrom; + } + + public void setDepartsFrom(String departsFrom) { + this.departsFrom = departsFrom; + } + + public Integer getTime() { + return time; + } + + public void setTime(Integer time) { + this.time = time; + } + + public Double getVolume() { + return volume; + } + + public void setVolume(Double volume) { + this.volume = volume; + } + + public Double getVolumePercentage() { + return volumePercentage; + } + + public void setVolumePercentage(Double volumePercentage) { + this.volumePercentage = volumePercentage; + } + + @Override + public String toString() { + return "{\"arrivesTo\":\"" + arrivesTo + "\", \"departsFrom\":\"" + departsFrom + "\", \"time\":" + time + + ", \"volume\":" + volume + ", \"volumePercentage\":" + volumePercentage + "}"; + } + + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/OriginDestinationMatrix.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/OriginDestinationMatrix.java new file mode 100644 index 0000000000000000000000000000000000000000..d60db4421a7936a7bea2596d485c52767e3a5f1e --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/OriginDestinationMatrix.java @@ -0,0 +1,268 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.DataModel.Population.CensusObserved; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class OriginDestinationMatrix { + + //Description: 'Origin - Destination Matrices' + //REF: https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/ODMatrix-ngsi.jsonld + + @Schema(description = "Aggregation type.", required = false) + private String aggregationType; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "NGSI Entity type. It has to be 'OriginDestinationMatrix'.", required = true) + private String type; + + @Schema(description = "Category.", required = false) + private String category; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String createdAt; + + @Schema(description = "End date.", required = false) + private String endDate; + + @Schema(description = "End period.", required = false) + private String endPeriod; + + @Schema(description = "Zones.", required = false) + private List<ODMatrixValue> matrixData; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String modifiedAt; + + @Schema(description = "Start date.", required = true) + private String startDate; + + @Schema(description = "Start period.", required = false) + private String startPeriod; + + @Schema(description = "Travel mode.", required = false) + private String travelMode; + + @Schema(description = "Zones.", required = false) + private String zones; + + @Schema(hidden = true) + private List<String> context; + + public String getAggregationType() { + return aggregationType; + } + + public void setAggregationType(String aggregationType) { + this.aggregationType = aggregationType; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getEndPeriod() { + return endPeriod; + } + + public void setEndPeriod(String endPeriod) { + this.endPeriod = endPeriod; + } + + public List<ODMatrixValue> getMatrixData() { + return matrixData; + } + + public void setMatrixData(List<ODMatrixValue> matrixData) { + this.matrixData = matrixData; + } + + public String getModifiedAt() { + return modifiedAt; + } + + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getStartPeriod() { + return startPeriod; + } + + public void setStartPeriod(String startPeriod) { + this.startPeriod = startPeriod; + } + + public String getTravelMode() { + return travelMode; + } + + public void setTravelMode(String travelMode) { + this.travelMode = travelMode; + } + + public String getZones() { + return zones; + } + + public void setZones(String zones) { + this.zones = zones; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public boolean isValid() { + + //Required fields: id, type, dateObserved + if (id == null || type == null || startDate == null) return false; + + //type: must be "OriginDestinationMatrix"; + if (type.compareTo("OriginDestinationMatrix") != 0) return false; + + + //startDate: valid date (YYYY-MM-DD format) + if (Utils.checkValidDate(startDate) == false) return false; + + //endDate: if present, valid date (YYYY-MM-DD format) + if (endDate != null && Utils.checkValidDate(endDate) == false) return false; + + //startPeriod: if present, valid time (HH:MM or HH:MM:SS format) + if (startPeriod != null && Utils.checkValidTime(startPeriod) == false) return false; + + //endPeriod: if present, valid time (HH:MM or HH:MM:SS format) + if (endPeriod != null && Utils.checkValidTime(endPeriod) == false) return false; + + //more checks? + + return true; + } + + public static OriginDestinationMatrix createOriginDestinationMatrix(String data) { + + OriginDestinationMatrix od = null; + + try { + + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + od = Utils.JSON2Object(obj.toString(), OriginDestinationMatrix.class); + + //put start and end periods in format HH:MM:SS + String stPer = od.getStartPeriod(); + if (stPer != null) + if (stPer.length() == 5) od.setStartPeriod(stPer + ":00"); + String endPer = od.getEndPeriod(); + if (endPer != null) + if (endPer.length() == 5) od.setEndPeriod(endPer + ":00"); + + od.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to CensusObserved: " + e.getMessage()); + } + + return od; + } + + @Override + public String toString() { + return "{\"aggregationType\":\"" + aggregationType + "\", \"id\":\"" + id + "\", \"type\":\"" + type + + "\", \"category\":\"" + category + "\", \"createdAt\":\"" + createdAt + "\", \"endDate\":\"" + endDate + + "\", \"endPeriod\":\"" + endPeriod + "\", \"matrixData\":" + matrixData + ", \"modifiedAt\":\"" + + modifiedAt + "\", \"startDate\":\"" + startDate + "\", \"startPeriod\":\"" + startPeriod + + "\", \"travelMode\":\"" + travelMode + "\", \"zones\":\"" + zones + "\", \"context\":\"" + context + + "\"}"; + } + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TrafficFlowObserved.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TrafficFlowObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..c19365188fb3db2a3345f2e35384f19cea46398b --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TrafficFlowObserved.java @@ -0,0 +1,517 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TrafficFlowObserved { + + // Description: 'An observation of traffic flow conditions at a certain place + // and time.' + // REF: + // https://github.com/smart-data-models/dataModel.Transportation/tree/master/TrafficFlowObserved + // TODO: add examples to @Schemas + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided.", required = false) + private String areaServed; + + @Schema(description = "Average gap distance (meters) between consecutive vehicles.", required = false) + private Double averageGapDistance; + + @Schema(description = "Average headway time (seconds). Headway time is the time ellapsed between two consecutive vehicles.", required = false) + private Double averageHeadwayTime; + + @Schema(description = "Average length (meters) of the vehicles transiting during.", required = false) + private Double averageVehicleLength; + + @Schema(description = "Average speed (Km/h) of the vehicles transiting during the observation period.", required = false) + private Double averageVehicleSpeed; + + @Schema(description = "Flags whether there was a traffic congestion during the observation period in the referred lane. The absence of this attribute means no traffic congestion.", required = false) + private Boolean congested; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTC format. It can be represented by an specific time instant or by an ISO8601 interval. As a workaround for the lack of support of Orion Context Broker for datetime intervals, it can be used two separate attributes: `dateObservedFrom`, `dateObservedTo`. [DateTime](https://schema.org/DateTime) or an ISO8601 interval represented as [Text](https://schema.org/Text).", required = true) + private String dateObserved; + + @Schema(description = "Observation period start date and time. See `dateObserved`", required = false) + private String dateObservedFrom; + + @Schema(description = "Observation period end date and time. See `dateObserved`", required = false) + private String dateObservedTo; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Total number of vehicles detected during this observation period.", required = false) + private Double intensity; + + @Schema(description = "Usual direction of travel in the lane referred by this observation. This attribute is useful when the observation is not referencing any road segment, allowing to know the direction of travel of the traffic flow observed. Enum:forward, backward''. See RoadSegment for a description of the semantics of these values.", required = false) + private String laneDirection; + + @Schema(description = "Lane identifier. Lane identification is done using the conventions defined by RoadSegment entity which are based on [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right).", required = false) + private Double laneId; + + @Schema(description = "GeoJSON Geometry.", required = false) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "Fraction of the observation time where a vehicle has been occupying the observed lane.", required = false) + private Double occupancy; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "Concerned road segment on which the observation has been made. Reference to an entity of type RoadSegment.", required = false) + private String refRoadSegment; // It's a reference! + + @Schema(description = "Flags whether traffic in the lane was reversed during the observation period. The absence of this attribute means no lane reversion.", required = false) + private Boolean reversedLane; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "sense of traffic", required = false) + private String sense; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. It has to be 'TrafficFlowObserved'.", required = true) + private String type; + + @Schema(description = "It allows to specify a sub type of `vehicleType`, eg if the `vehicleType` is set to `Lorry` the `vehicleSubType` may be `OGV1` or `OGV2` to convey more information about the exact type of vehicle.", required = false) + private String vehicleSubType; + + @Schema(description = "Type of vehicle from the point of view of its structural characteristics. Enum:''agriculturalVehicle, bicycle, bus, minibus, car, caravan, tram, tanker, carWithCaravan, carWithTrailer, lorry, moped, motorcycle, motorcycleWithSideCar, motorscooter, trailer, van, constructionOrMaintenanceVehicle, trolley, binTrolley, sweepingMachine, cleaningTrolley''.", required = false) + private VehicleTypeEnum vehicleType; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public double getAverageGapDistance() { + return averageGapDistance; + } + + public void setAverageGapDistance(double averageGapDistance) { + this.averageGapDistance = averageGapDistance; + } + + public double getAverageHeadwayTime() { + return averageHeadwayTime; + } + + public void setAverageHeadwayTime(double averageHeadwayTime) { + this.averageHeadwayTime = averageHeadwayTime; + } + + public double getAverageVehicleLength() { + return averageVehicleLength; + } + + public void setAverageVehicleLength(double averageVehicleLength) { + this.averageVehicleLength = averageVehicleLength; + } + + public double getAverageVehicleSpeed() { + return averageVehicleSpeed; + } + + public void setAverageVehicleSpeed(double averageVehicleSpeed) { + this.averageVehicleSpeed = averageVehicleSpeed; + } + + public boolean isCongested() { + return congested; + } + + public void setCongested(boolean congested) { + this.congested = congested; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateObservedFrom() { + return dateObservedFrom; + } + + public void setDateObservedFrom(String dateObservedFrom) { + this.dateObservedFrom = dateObservedFrom; + } + + public String getDateObservedTo() { + return dateObservedTo; + } + + public void setDateObservedTo(String dateObservedTo) { + this.dateObservedTo = dateObservedTo; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public double getIntensity() { + return intensity; + } + + public void setIntensity(double intensity) { + this.intensity = intensity; + } + + public String getLaneDirection() { + return laneDirection; + } + + public void setLaneDirection(String laneDirection) { + this.laneDirection = laneDirection; + } + + public double getLaneId() { + return laneId; + } + + public void setLaneId(double laneId) { + this.laneId = laneId; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getOccupancy() { + return occupancy; + } + + public void setOccupancy(double occupancy) { + this.occupancy = occupancy; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getRefRoadSegment() { + return refRoadSegment; + } + + public void setRefRoadSegment(String refRoadSegment) { + this.refRoadSegment = refRoadSegment; + } + + public boolean isReversedLane() { + return reversedLane; + } + + public void setReversedLane(boolean reversedLane) { + this.reversedLane = reversedLane; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSense() { + return sense; + } + + public void setSense(String sense) { + this.sense = sense; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getVehicleSubType() { + return vehicleSubType; + } + + public void setVehicleSubType(String vehicleSubType) { + this.vehicleSubType = vehicleSubType; + } + + public VehicleTypeEnum getVehicleType() { + return vehicleType; + } + + public void setVehicleType(VehicleTypeEnum vehicleType) { + this.vehicleType = vehicleType; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + @Schema(hidden = true) + public boolean isValid() { + + // Required fields: id and type + if (id == null || type == null) + return false; + + // Required dateObserved or dateObservedFrom + dateObservedTo + if (dateObserved == null) { + if (dateObservedFrom == null || dateObservedTo == null) + return false; + else { + // valid dates + if (Utils.ISO2Date(dateObservedFrom) == null) + return false; + if (Utils.ISO2Date(dateObservedTo) == null) + return false; + } + } + else { + // valid date + if (Utils.ISO2Date(dateObserved) == null) + return false; + } + // type: must be "TrafficFlowObserved"; + if (type.compareTo("TrafficFlowObserved") != 0) + return false; + + // Minimum/maximum values + if (averageGapDistance != null && averageGapDistance < 0) + return false; + if (averageHeadwayTime != null && averageHeadwayTime < 0) + return false; + if (averageVehicleLength != null && averageVehicleLength < 0) + return false; + if (averageVehicleSpeed != null && averageVehicleSpeed < 0) + return false; + if (intensity != null && intensity < 0) + return false; + if (laneId != null && laneId < 1) + return false; + if (occupancy != null && (occupancy < 0 || occupancy > 1)) + return false; + + return true; + } + + public static TrafficFlowObserved createTrafficFlowObserved(String data) { + + TrafficFlowObserved tf = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add(arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + tf = Utils.JSON2Object(obj.toString(), TrafficFlowObserved.class); + tf.setLocation(pol); + tf.setContext(cnt); + + } catch (Exception e) { + System.out + .println(" Exception converting JSON [" + data + "] to TrafficFlowObserved: " + e.getMessage()); + } + + return tf; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + + areaServed + "\", \"averageGapDistance\":\"" + averageGapDistance + "\", \"averageHeadwayTime\":\"" + + averageHeadwayTime + "\", \"averageVehicleLength\":\"" + averageVehicleLength + + "\", \"averageVehicleSpeed\":\"" + averageVehicleSpeed + "\", \"congested\":\"" + congested + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"dateObservedFrom\":\"" + dateObservedFrom + "\", \"dateObservedTo\":\"" + dateObservedTo + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"intensity\":\"" + intensity + + "\", \"laneDirection\":\"" + laneDirection + "\", \"laneId\":\"" + laneId + "\", \"location\":\"" + + location + "\", \"name\":\"" + name + "\", \"occupancy\":\"" + occupancy + "\", \"owner\":\"" + owner + + "\", \"refRoadSegment\":\"" + refRoadSegment + "\", \"reversedLane\":\"" + reversedLane + + "\", \"seeAlso\":\"" + seeAlso + "\", \"source\":\"" + source + "\", \"type\":\"" + type + + "\", \"sense\":\"" + sense + "\", \"vehicleSubType\":\"" + vehicleSubType + "\", \"vehicleType\":\"" + + vehicleType + "\", \"context\":\"" + context + "\"}"; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TransportationDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TransportationDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..db8dc972a4eafcfe40d01a0116f2ddd74251a948 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TransportationDataModel.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +public enum TransportationDataModel { + TRAFFICFLOWOBSERVED, + //CROWDFLOWOBSERVED, + ORIGINDESTINATIONMATRIX, + VEHICLEOBSERVED +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/Vehicle.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/Vehicle.java new file mode 100644 index 0000000000000000000000000000000000000000..2416af7c1d15d45b9dcb6001f7455b68db9301ca --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/Vehicle.java @@ -0,0 +1,220 @@ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; +import org.springframework.util.ObjectUtils; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Vehicle { + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "NGSI Entity type. It has to be 'Vehicle'.", required = true) + private String type; + + @Schema(description = " Type of vehicle from the point of view of its structural characteristics.", required = true) + private String vehicleType ; + + @Schema(description = "GeoJSON Geometry.", required = false) + private LocationGeojson location; + + @Schema(description = "Denotes the magnitude of the horizontal component of the vehicle's current velocity and is specified in Kilometers per Hour. If provided, the value of the speed attribute must be a non-negative real number. -1 MAY be used if speed is transiently unknown for some reason", required = false) + private Double speed; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String dateModified; + + @Schema(description = "Last reported time of observation", required = true) + private String observationDateTime; + + @Schema(description = "Denotes the direction of travel of the vehicle and is specified in decimal degrees, where 0 <= heading < 360, counting clockwise relative to the true north. If the vehicle is stationary (i.e. the value of the speed attribute is 0), then the value of the heading attribute must be equal to -1", required = false) + private Double heading; + + @Schema(hidden = true) + private List<String> context; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getVehicleType() { + return vehicleType; + } + + public void setVehicleType(String vehicleType) { + this.vehicleType = vehicleType; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public Double getSpeed() { + return speed; + } + + public void setSpeed(Double speed) { + this.speed = speed; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getObservationDateTime() { + return observationDateTime; + } + + public void setObservationDateTime(String observationDateTime) { + this.observationDateTime = observationDateTime; + } + + public Double getHeading() { + return heading; + } + + public void setHeading(Double heading) { + this.heading = heading; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + @Schema(hidden = true) + public boolean isValid() { + + // Required fields: id and type + if (id == null || type == null || observationDateTime == null) + return false; + if (Utils.checkValidDate(observationDateTime) == false) return false; + + if (!type.equals("Vehicle")) return false; + + if (!ObjectUtils.containsConstant(VehicleTypeEnum.values(),vehicleType,true )) return false; + + + return true; + } + + public static Vehicle createVehicle(String data) { + + Vehicle tf = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add(arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + tf = Utils.JSON2Object(obj.toString(), Vehicle.class); + tf.setLocation(pol); + tf.setContext(cnt); + + } catch (Exception e) { + System.out + .println(" Exception converting JSON [" + data + "] to Vehicle: " + e.getMessage()); + } + + return tf; + } + + @Override + public String toString() { + return "{}"; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/VehicleTypeEnum.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/VehicleTypeEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..82c260b958cf44fc604476e97308c771b0968342 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/VehicleTypeEnum.java @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +public enum VehicleTypeEnum { + agriculturalVehicle, + bicycle, + eBike, + bus, + minibus, + car, + caravan, + tram, + tanker, + carWithCaravan, + carWithTrailer, + lorry, + moped, + motorcycle, + motorcycleWithSideCar, + motorscooter, + trailer, + van, + constructionOrMaintenanceVehicle, + trolley, + binTrolley, + sweepingMachine, + cleaningTrolley, + pedestrian +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TypeOfLocation.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TypeOfLocation.java new file mode 100644 index 0000000000000000000000000000000000000000..5885f9037afc20578d22044a03f3d40c614661ca --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TypeOfLocation.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum TypeOfLocation { + indoor, + outdoor +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/PressureTendency.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/PressureTendency.java new file mode 100644 index 0000000000000000000000000000000000000000..3676c46b5f5e30e2e37ec7383211f14e836af18c --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/PressureTendency.java @@ -0,0 +1,22 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Weather; + +public enum PressureTendency { + falling, + raising, + steady +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherDataModel.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..bdffb37865ad7f86c0e20f5dd98709e2ef2738dc --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Weather; + +public enum WeatherDataModel { + WEATHEROBSERVED +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherObserved.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..cf74fbfad63dbcbb478ac2c0579e3163fb8563ec --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherObserved.java @@ -0,0 +1,498 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Weather; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.EnumUtils; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class WeatherObserved { + + /* + TODO: + There's a mismatch between the definition and the examples. Examples have some fields that are not present in the definition: + atmosphericPressure + illuminance + stationCode and stationName (could be part of the referenced device?) + windSpeed + windDirection + Fields atmosphericPressure, windSpeed and windDirection can be relevant, so they are added to the original model + */ + + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided.", required = false) + private String areaServed; + + @Schema(description = "Atmospheric pressure of the item.", required = false) + private Double atmosphericPressure; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "Date of the observed entity defined by the user, in ISO8601 UTCformat.", required = true) + private String dateObserved; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "The dew point encoded as a number.", required = false) + private Double dewPoint; + + @Schema(description = "Temperature appreciation of the item.", required = false) + private Double feelsLikesTemperature; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "Amount of water rain registered. Units: 'Liters per square meter'.", required = false) + private Double precipitation; + + @Schema(description = "Is the pressure rising or falling? It can be expressed in quantitative terms or qualitative terms.", required = false) + private Object pressureTendency; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "Humidity in the Air.", required = false) + private Double relativeHumidity; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "The snow height observed by generic snow depth measurement sensors, expressed in centimeters.", required = false) + private Double snowHeight; + + @Schema(description = "The solar radiation observed measured in Watts per square.", required = false) + private Double solarRadiation; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "The water level surface elevation observed by Hydrometric measurement sensors, namely a [Stream Gauge](https://en.wikipedia.org/wiki/Stream_gauge) expressed in centimeters.", required = false) + private Double streamGauge; + + @Schema(description = "Temperature of the item.", required = false) + private Double temperature; + + @Schema(description = "NGSI Entity type. Must be 'WeatherObserved'.", required = true) + private String type; + + @Schema(description = "The maximum UV index for the period, based on the World Health Organization''s UV Index measure. [http://www.who.int/uv/intersunprogramme/activities/uv_index/en/](http://www.who.int/uv/intersunprogramme/activities/uv_index/en/).", required = false) + private Double uvIndexMax; + + @Schema(description = "Direction of the wind.", required = false) + private Double windDirection; + + @Schema(description = "Intensity of the wind.", required = false) + private Double windSpeed; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public Double getAtmosphericPressure() { + return atmosphericPressure; + } + + public void setAtmosphericPressure(Double atmosphericPressure) { + this.atmosphericPressure = atmosphericPressure; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Double getDewPoint() { + return dewPoint; + } + + public void setDewPoint(Double dewPoint) { + this.dewPoint = dewPoint; + } + + public Double getFeelsLikesTemperature() { + return feelsLikesTemperature; + } + + public void setFeelsLikesTemperature(Double feelsLikesTemperature) { + this.feelsLikesTemperature = feelsLikesTemperature; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public Double getPrecipitation() { + return precipitation; + } + + public void setPrecipitation(Double precipitation) { + this.precipitation = precipitation; + } + + public Object getPressureTendency() { + return pressureTendency; + } + + public void setPressureTendency(PressureTendency pressureTendency) { + this.pressureTendency = pressureTendency; + } + public void setPressureTendency(Double pressureTendency) { + this.pressureTendency = pressureTendency; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public Double getRelativeHumidity() { + return relativeHumidity; + } + + public void setRelativeHumidity(Double relativeHumidity) { + this.relativeHumidity = relativeHumidity; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public Double getSnowHeight() { + return snowHeight; + } + + public void setSnowHeight(Double snowHeight) { + this.snowHeight = snowHeight; + } + + public Double getSolarRadiation() { + return solarRadiation; + } + + public void setSolarRadiation(Double solarRadiation) { + this.solarRadiation = solarRadiation; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Double getStreamGauge() { + return streamGauge; + } + + public void setStreamGauge(Double streamGauge) { + this.streamGauge = streamGauge; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Double getUvIndexMax() { + return uvIndexMax; + } + + public void setUvIndexMax(Double uvIndexMax) { + this.uvIndexMax = uvIndexMax; + } + + public Double getWindDirection() { + return windDirection; + } + + public void setWindDirection(Double windDirection) { + this.windDirection = windDirection; + } + + public Double getWindSpeed() { + return windSpeed; + } + + public void setWindSpeed(Double windSpeed) { + this.windSpeed = windSpeed; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved, location + if (id == null || type == null || dateObserved == null || location == null) return false; + + //DateObserved: valid date + if (Utils.ISO2Date(dateObserved) == null) return false; + + //type: must be "WeatherObserved"; + if (type.compareTo("WeatherObserved") != 0) return false; + + + + //Minimum/maximum values + if (atmosphericPressure != null && atmosphericPressure < 0) return false; + if (precipitation != null && precipitation < 0) return false; + if (snowHeight != null && snowHeight < 0) return false; + if (solarRadiation != null && solarRadiation < 0) return false; + if (streamGauge != null && streamGauge < 0) return false; + if (relativeHumidity!= null && (relativeHumidity < 0 || relativeHumidity > 1)) return false; + if (uvIndexMax != null && uvIndexMax < 1) return false; + if (windDirection!= null && (windDirection < -180 || windDirection > 180)) return false; + if (windSpeed != null && windSpeed < 0) return false; + + //pressureTendency: numeric or "PressureTendency" + if (pressureTendency != null) { + if (pressureTendency instanceof Number == false && pressureTendency instanceof String == false ) return false; + else { + if (pressureTendency instanceof String) { + if (EnumUtils.isValidEnum(PressureTendency.class, pressureTendency.toString()) == false) return false; + } + } + } + + //all ok + return true; + } + + public static WeatherObserved createWeatherObserved(String data) { + + WeatherObserved weather = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + weather = Utils.JSON2Object(obj.toString(), WeatherObserved.class); + weather.setLocation(pol); + weather.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to WeatherObserved: " + e.getMessage()); + } + + return weather; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + + areaServed + "\", \"atmosphericPressure\":\"" + atmosphericPressure + "\", \"dataProvider\":\"" + + dataProvider + "\", \"dateCreated\":\"" + dateCreated + "\", \"dateModified\":\"" + dateModified + + "\", \"dateObserved\":\"" + dateObserved + "\", \"description\":\"" + description + + "\", \"dewPoint\":\"" + dewPoint + "\", \"feelsLikesTemperature\":\"" + feelsLikesTemperature + + "\", \"id\":\"" + id + "\", \"location\":\"" + location + "\", \"name\":\"" + name + + "\", \"owner\":\"" + owner + "\", \"precipitation\":\"" + precipitation + + "\", \"pressureTendency\":\"" + pressureTendency + "\", \"refDevice\":\"" + refDevice + + "\", \"relativeHumidity\":\"" + relativeHumidity + "\", \"seeAlso\":\"" + seeAlso + + "\", \"snowHeight\":\"" + snowHeight + "\", \"solarRadiation\":\"" + solarRadiation + + "\", \"source\":\"" + source + "\", \"streamGauge\":\"" + streamGauge + "\", \"temperature\":\"" + + temperature + "\", \"type\":\"" + type + "\", \"uvIndexMax\":\"" + uvIndexMax + + "\", \"windDirection\":\"" + windDirection + "\", \"windSpeed\":\"" + windSpeed + "\", \"context\":\"" + + context + "\"}"; + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Response.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Response.java new file mode 100644 index 0000000000000000000000000000000000000000..1385be069c22935e2eed496c8aee4ee5746aefdb --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Response.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import org.springframework.http.HttpStatus; + +public class Response +{ + private final HttpStatus status; + private final String data; + + public Response(HttpStatus status, String data) + { + this.status = status; + this.data = data; + } + + public HttpStatus getStatus() { + return status; + } + + public String getData() { + return data; + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/JsonDateTimeConverter.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/JsonDateTimeConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..345f1066e6dd21cc29a7f34b000f8a9cf0172095 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/JsonDateTimeConverter.java @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.Utils; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +import org.bson.json.Converter; +import org.bson.json.StrictJsonWriter; + +public class JsonDateTimeConverter implements Converter<Long> { + + static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("UTC")); + + @Override + public void convert(Long value, StrictJsonWriter writer) { + try { + Instant instant = new Date(value).toInstant(); + String s = DATE_TIME_FORMATTER.format(instant); + writer.writeString(s); + } catch (Exception e) { + System.out.println(" Fail to convert " + value + " to JSON date: " + e.getMessage()); + } + } + +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/StringToEnumConverter.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/StringToEnumConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..2c4cda1ae2e33749e3508317b9d97030aeba74d6 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/StringToEnumConverter.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.Utils; + +import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +import org.springframework.core.convert.converter.Converter; + +public class StringToEnumConverter implements Converter<String, AggregatorEnum> +{ + @Override + public AggregatorEnum convert(String source) { + return AggregatorEnum.valueOf(source.toUpperCase()); + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/Utils.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..3598876d4f6fd76c9f11903e84454db7cf112bd5 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/Utils.java @@ -0,0 +1,360 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.Utils; + +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.riot.RDFFormat; +import org.apache.jena.vocabulary.DCTerms; +import org.bson.BsonValue; +import org.bson.Document; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mongodb.DBRef; +import com.tecnalia.urbanite.storage.APIResponse; + +public class Utils { + + private static DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static DateFormat dfOnlyDate = new SimpleDateFormat("yyyy-MM-dd"); + private static DateFormat dfOnlyTime = new SimpleDateFormat("HH:mm:ss"); + private static DateFormat dfOnlyTime2 = new SimpleDateFormat("HH:mm"); + + public static <T> T JSON2Object(String input, Class<T> clazz) { + + try { + Gson g = new Gson(); + T p = g.fromJson(input, clazz); + return p; + } catch (Exception ex) { + System.err.println(" Exception converting JSON [" + input + "] to Object [" + clazz.getName() + "]: " + ex.getMessage()); + throw ex; + } + } + public static <T> T JSON2Object(String input, Class<T> clazz,FieldNamingPolicy policy) { + + try { + Gson g = new GsonBuilder() + .setFieldNamingPolicy(policy) + .create() ; + T p = g.fromJson(input, clazz); + return p; + } catch (Exception ex) { + System.err.println(" Exception converting JSON [" + input + "] to Object [" + clazz.getName() + "]: " + ex.getMessage()); + throw ex; + } + } + + public static String Object2JSON (Object o) { + try { + Gson g = new Gson(); + return g.toJson(o); + } catch (Exception ex) { + System.err.println(" Exception converting object [" + o.getClass().getName() + "] to String: " + ex.getMessage()); + return null; + } + } + public static String Object2JSON (Object o, FieldNamingPolicy policy) { + try { + Gson g = new GsonBuilder() + .setFieldNamingPolicy(policy) + .create() ; + return g.toJson(o); + } catch (Exception ex) { + System.err.println(" Exception converting object [" + o.getClass().getName() + "] to String: " + ex.getMessage()); + return null; + } + } + public static String Date2ISO(Date input) { + try { + String dt = df.format(input); + return dt; + } catch (Exception ex) { + System.err.println(" Exception converting date " + input + " to ISO8601 UTC format: " + ex.getMessage()); + return ""; + } + } + + public static int getDateYear(Date date) { + if (date == null) return 0; + LocalDateTime lstartDate =LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC")); + return lstartDate.getYear(); + + } + public static int getDateMonth(Date date) { + if (date == null) return 0; + LocalDateTime lstartDate =LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC")); + return lstartDate.getMonthValue(); + + } + public static Date ISO2Date(String input) { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + try { + df.setTimeZone(TimeZone.getTimeZone("UTC")); + Date dt = df.parse(input); + return dt; + } catch (Exception ex) { + try { + df2.setTimeZone(TimeZone.getTimeZone("UTC")); + Date dt = df2.parse(input); + return dt; + } catch (Exception ex2) { + System.err.println(" Date [" + input + "] is not ISO 8601 format: " + ex2.getMessage()); + return null; + } + } + } + + public static String addJsonLdModels(String jsonld1, String jsonld2) { + if (jsonld1 == null || jsonld1.isEmpty()) { + return jsonld2; + } + + if (jsonld2 == null || jsonld2.isEmpty()) { + return jsonld1; + } + + Model m1 = ModelFactory.createDefaultModel(); + try (StringReader reader = new StringReader(jsonld1)) { + m1.read(reader, null, "JSON-LD"); + } + + try (StringReader reader = new StringReader(jsonld2)) { + m1.read(reader, null, "JSON-LD"); + } + + + try { + updateIssuedDates(m1); + updateModifiedDates(m1); + } catch (Exception ex) { + System.err.println ("Error updating issued dates"); + } + + + StringWriter out = new StringWriter(); + RDFDataMgr.write(out, m1, RDFFormat.JSONLD_PRETTY); + + return out.toString(); + } + + public static Model updateIssuedDates(Model m) throws Exception { + // Get the property and the subject + Property issued = m.getProperty(DCTerms.getURI() + "issued"); + String sd = null; + + // Get all statements/triples of the form (****, issued, ***) + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + Date issDate = null; + Statement issDateStmt = null; + for (Statement st:m.listStatements(null, issued, sd).toList()) { + + if (issDate ==null) { + issDateStmt = st; + issDate = formatter.parse(st.getString()); + } else { + Date scur = formatter.parse(st.getString()); + + //for issued date we keep the oldest date + if (scur.before(issDate)) { + //delete from model + m.remove(issDateStmt); + issDateStmt = st; + issDate = scur; + } else { + m.remove(st); + } + } + + } + + return m; + } + + public static Model updateModifiedDates(Model m) throws Exception { + // Get the property and the subject + Property modified = m.getProperty(DCTerms.getURI() + "modified"); + String sd = null; + + // Get all statements/triples of the form (****, modified, ***) + SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + Date issDate = null; + Statement issDateStmt = null; + for (Statement st:m.listStatements(null, modified, sd).toList()) { + + if (issDate ==null) { + issDateStmt = st; + issDate = formatter.parse(st.getString()); + } else { + Date scur = formatter.parse(st.getString()); + + //for modified date we keep the latest date + if (scur.after(issDate)) { + //delete from model + m.remove(issDateStmt); + issDateStmt = st; + issDate = scur; + } else { + m.remove(st); + } + } + + } + + return m; + } + + public static ResponseEntity<Object> formatResponse(APIResponse res) { + return formatResponse(res, false); + } + + public static ResponseEntity<Object> formatResponse(APIResponse res, boolean returnAsArray) { + + if (res.getStatus() != HttpStatus.OK) { + JSONObject jsError = new JSONObject(); + try { + jsError.put("Error", res.getError()); + } catch (JSONException e) { + //shouldn't be any error, it's just creating a JSON! + } + return new ResponseEntity<Object>(jsError.toString(), new HttpHeaders(), res.getStatus()); + } + else { + List<JSONObject> data = res.getData(); + if (returnAsArray) + return new ResponseEntity<Object>(data.toString(), new HttpHeaders(), res.getStatus()); + else { + //return a Object if only one element present + if (data.size() == 1) + return new ResponseEntity<Object>(data.get(0).toString(), new HttpHeaders(), res.getStatus()); + else + return new ResponseEntity<Object>(data.toString(), new HttpHeaders(), res.getStatus()); + } + } + } + + public static Object BsonValue2JavaType(BsonValue value) { + switch (value.getBsonType()) { + case INT32: + return value.asInt32().getValue(); + case INT64: + return value.asInt64().getValue(); + case STRING: + return value.asString().getValue(); + case DECIMAL128: + return value.asDecimal128().doubleValue(); + case DOUBLE: + return value.asDouble().getValue(); + case BOOLEAN: + return value.asBoolean().getValue(); + case OBJECT_ID: + return value.asObjectId().getValue(); + case DB_POINTER: + return new DBRef(value.asDBPointer().getNamespace(), value.asDBPointer().getId()); + case BINARY: + return value.asBinary().getData(); + case DATE_TIME: + return new Date(value.asDateTime().getValue()); + case SYMBOL: + return value.asSymbol().getSymbol(); + case ARRAY: + return value.asArray().toArray(); + case DOCUMENT: + return Document.parse(value.asDocument().toJson()); + default: + return value; + } + } + + public static <T> boolean typeHasFields(Class<T> type, List<String> fields) { + + if (fields != null && fields.isEmpty() == false) { + Field[] allTypeFields = type.getDeclaredFields(); + + List<String> lstFieldNames = new ArrayList<String>(); + for (Field f: allTypeFields) + lstFieldNames.add(f.getName()); + + for (String fieldName : fields) { + if (lstFieldNames.contains(fieldName) == false) + return false; + } + } + return true; + } + + public static <T> boolean typeHasFieldsJSON(Class<T> type, JSONObject fields) { + + List<String> lstFieldNames = new ArrayList<String>(); + Iterator<String> keys = fields.keys(); + while(keys.hasNext()) + lstFieldNames.add(keys.next()); + + return typeHasFields(type, lstFieldNames); + } + + public static boolean checkValidDate(String input) { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + try { + Date dt = dfOnlyDate.parse(input); + return true; + } catch (Exception ex) { + System.err.println(" Date [" + input + "] is not ISO 8601 format: " + ex.getMessage()); + return false; + } + } + + public static boolean checkValidTime(String input) { + + try { + Date dt = dfOnlyTime.parse(input); + return true; + } catch (Exception ex) { + try { + Date dt = dfOnlyTime2.parse(input); + return true; + } catch (Exception ex2) { + System.err.println(" Date [" + input + "] is not ISO 8601 format: " + ex2.getMessage()); + return false; + } + } + + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AggregatorController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AggregatorController.java new file mode 100644 index 0000000000000000000000000000000000000000..ff5947f011355cd920a6113f9e55b253f9909da5 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AggregatorController.java @@ -0,0 +1,68 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import com.tecnalia.opentsdb.client.OpentsdbClient; +import com.tecnalia.opentsdb.client.query.MetriQueryBuilder; +import com.tecnalia.opentsdb.client.query.QueryBuilder; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.Response; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import java.util.Objects; + +public class AggregatorController implements IAggregatorController +{ + private final Logger logger = LoggerFactory.getLogger(AggregatorController.class); + + @Override + public Response aggregate(City city, String compoundMetric, Date start, Date end, AggregatorEnum aggregator, String downsample, Map<String,String> tags) + { + Response response; + + try + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + HttpResponse httpResponse = opentsdbClient.Get( new QueryBuilder() + .addStart( Objects.requireNonNull((start)).getTime() /1000) + .addEnd( Objects.requireNonNull((end)).getTime() /1000) + .addMetricQuery(new MetriQueryBuilder() + .addAggregator(aggregator.name()).addMetric(compoundMetric).addDownsample(downsample).addTags(tags) + .build()) + .build()); + + response = new Response(HttpStatus.valueOf(httpResponse.getStatusLine().getStatusCode()), EntityUtils.toString(httpResponse.getEntity())); + + } + catch (IOException ex) + { + logger.error(ex.getMessage(), ex); + response = new Response(HttpStatus.INTERNAL_SERVER_ERROR, "It could not connect with the opentsdb server."); + } + + return response; + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AirQualityObservedController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AirQualityObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..5d7feb8060f7749dc02aa1608f89dc89c371ed5f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AirQualityObservedController.java @@ -0,0 +1,428 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.AirQualityObserved; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class AirQualityObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(AirQualityObservedController.class); + + public AirQualityObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("AirQualityObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> airColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + airColls.add(collectionName); + } + + for (int i = 0; i < arrData.length(); i++) { + AirQualityObserved record = AirQualityObserved.createAirQualityObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (airColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + doc.append("dateObserved", dateObs); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + /* + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + JSONObject oIns = new JSONObject(); + JSONObject oNoIns = new JSONObject(); + JSONObject oUpd = new JSONObject(); + try { + oIns.put("inserted", arrInserted); + oNoIns.put("notInserted", arrNotInserted); + oUpd.put("updated", arrUpdated); + lstRes.add(oIns); + lstRes.add(oNoIns); + lstRes.add(oUpd); + } catch (JSONException e) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + */ + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Air Quality Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Air Quality Observation' objects)"); + } + + logger.debug("AirQualityObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "AirQualityObserved", AirQualityObserved.class); + } + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("AirQualityObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + AirQualityObserved record = AirQualityObserved.createAirQualityObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + String collectionName = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Air Quality Observation' object)"); + } + + logger.debug("AirQualityObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "AirQualityObserved"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "AirQualityObserved", AirQualityObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"co\": 500,\r\n" + + " \"coLevel\": \"moderate\",\r\n" + + " \"no\": 45,\r\n" + + " \"no2\": 69,\r\n" + + " \"nox\": 139,\r\n" + + " \"so2\": 11,\r\n" + + " \"address\": {\r\n" + + " \"addressCountry\": \"ES\",\r\n" + + " \"addressLocality\": \"Madrid\",\r\n" + + " \"streetAddress\": \"Plaza de Espa\\u00f1a\",\r\n" + + " \"type\": \"PostalAddress\"\r\n" + + " },\r\n" + + " \"airQualityIndex\": 65,\r\n" + + " \"airQualityLevel\": \"moderate\",\r\n" + + " \"areaServed\": \"Brooklands\",\r\n" + + " \"dateObserved\": \"2016-03-15T11:00:00Z\",\r\n" + + " \"id\": \"urn:ngsi-ld:AirQualityObserved:Madrid-AmbientObserved-28079004-2016-03-15T11:00:00\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -3.712247222222222,\r\n" + + " 40.423852777777775\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"typeOfLocation\": \"outdoor\",\r\n" + + " \"precipitation\": 0,\r\n" + + " \"relativeHumidity\": 0.54,\r\n" + + " \"reliability\": 0.7,\r\n" + + " \"source\": \"http://datos.madrid.es\",\r\n" + + " \"temperature\": 12.2,\r\n" + + " \"type\": \"AirQualityObserved\",\r\n" + + " \"windDirection\": 166,\r\n" + + " \"windSpeed\": 0.64\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("AirQualityObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "AirQualityObserved",AirQualityObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "AirQualityObserved",AirQualityObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "AirQualityObserved"); + + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CalendarController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CalendarController.java new file mode 100644 index 0000000000000000000000000000000000000000..ab0d43e694444fd2711a7ac442843d1d43775f53 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CalendarController.java @@ -0,0 +1,1019 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Time.Calendar; +import com.tecnalia.urbanite.storage.DataModel.Time.DaySpecification; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class CalendarController extends GenericController implements IGenericController{ + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(CalendarController.class); + + public CalendarController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + logger.debug("Calendar::insertData(" + city + ")::IN"); + + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + //check data + if (data.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data not found."); + } + else { + + try { + JSONArray jsArrData = new JSONArray(data); + + if (jsArrData.length() != 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String DayCollectionName = (TimeDataModel.DAYSPECIFICATION+ "_" + city).toLowerCase(); + String CalCollectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + + MongoCollection<Document> dayColl = database.getCollection(DayCollectionName); + MongoCollection<Document> calColl = database.getCollection(CalCollectionName); + + //separate DaySpecification and Calendar objects + Map<String, DaySpecification> mapDaySpecs = new HashMap<>(); + //List<DaySpecification> arrDaySpecs = new ArrayList<DaySpecification>(); + List<Calendar> arrCalendars = new ArrayList<Calendar>(); + + for (int i = 0; i < jsArrData.length(); i++) { + + JSONObject jsData = jsArrData.getJSONObject(i); + String type = jsData.getString("type"); + + if (type != null && type.isEmpty() == false) { + + if (type.compareToIgnoreCase("DaySpecification") == 0) { + DaySpecification day = DaySpecification.createDaySpecification(jsData.toString()); + if (day!= null) { + if (day.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", day.getId()); + o.put("reason", "Wrong input data [DaySpecification]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else + mapDaySpecs.put(day.getId(), day); + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [DaySpecification]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else if (type.compareToIgnoreCase("Calendar") == 0) { + Calendar cal = Calendar.createCalendar(jsData.toString()); + if (cal != null) { + if (cal.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else + arrCalendars.add(cal); + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [Calendar]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' must be 'Calendar' or 'DaySpecification'"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' not found"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } + + + /* Here we have: + * mapDaySpecs: map of valid DaySpecification objects + * arrCalendars: list of valid Calendar objects + * First, we'll check (for each calendar) if all it's days are in the input data or already present in database + * Next, we'll try to insert/update only the days of the calendar. If there's no error, the calendar itself will be inserted/updated + */ + + for (Calendar cal: arrCalendars) { + //check days + List<String> missingDays = new ArrayList<String>(); + List<String> calDays = cal.getDays(); + for (String dayId: calDays) { + //day in input data? + if (mapDaySpecs.containsKey(dayId) == false) { + //not in input data. In database? + if (dayColl.find(Filters.eq("_id", dayId)).first() == null) + missingDays.add(dayId); + //else exists + } //else will be inserted or updated + } + + if (missingDays.size() > 0) { + //some day not found --> error + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]: days " + missingDays + " not found." ); + arrNotInserted.put(o); + } catch (JSONException ex) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON: " + ex.getMessage()); + } + } + else { + //all it's days are present in input data or database --> insert/update them + List<String> errorDays = new ArrayList<String>(); + for (String dayId: calDays) { + DaySpecification daySpec = mapDaySpecs.get(dayId); + if (daySpec != null) { + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(daySpec)); + //need to replace "id" for "_id" + doc.put("_id", daySpec.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", daySpec.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + dayColl.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrInserted.put(o); + mapDaySpecs.remove(dayId); + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = dayColl.find(Filters.eq("_id", daySpec.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + dayColl.replaceOne(Filters.eq("_id", daySpec.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrUpdated.put(o); + mapDaySpecs.remove(dayId); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + } + } + else { + errorDays.add(daySpec.getId()); + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + + } + } + } + } + + for (String dayId : mapDaySpecs.keySet()) { + JSONObject o = new JSONObject(); + try { + o.put("id", dayId); + o.put("reason", "Unreferenced DaySpecification."); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + // errors? + if (errorDays.isEmpty()) { + //insert calendar + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(cal)); + //need to replace "id" for "_id" + doc.put("_id", cal.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", cal.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + calColl.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", cal.getId()); + arrInserted.put(o); + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = calColl.find(Filters.eq("_id", cal.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + calColl.replaceOne(Filters.eq("_id", cal.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", cal.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Some of the days could not be inserted in database"); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + /* + JSONObject oIns = new JSONObject(); + JSONObject oNoIns = new JSONObject(); + JSONObject oUpd = new JSONObject(); + try { + oIns.put("inserted", arrInserted); + oNoIns.put("notInserted", arrNotInserted); + oUpd.put("updated", arrUpdated); + lstRes.add(oIns); + lstRes.add(oNoIns); + lstRes.add(oUpd); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + */ + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is empty."); + } + + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + logger.debug("Calendar::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + logger.debug("Calendar::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(Calendar.class, filters)) { + //check the return fields + if (Utils.typeHasFields(Calendar.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + DateFormat dateFormat = new SimpleDateFormat("yyyy"); + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) { + int yearStart = Integer.parseInt(dateFormat.format(startDate)); + dateRange.put("$gte", yearStart); + } + if (endDate != null) { + int yearEnd= Integer.parseInt(dateFormat.format(endDate)); + dateRange.put("$lte", yearEnd); + } + if (dateRange.isEmpty() == false) queryFilters.append("year", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("Calendar::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort= new BasicDBObject(); + //querySort.append("year", 1); + querySort.append("year",sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and year + if (returnFields.contains("year") == false) returnFields.add("year"); + projection = Projections.fields(Projections.include(returnFields)); + } + + FindIterable<Document> cursor; + if (limit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(limit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + //get days of the calendar + Calendar cal = Calendar.createCalendar(sDoc); + if (cal != null) { + List<JSONObject> arrDays = getDaysOfCalendar(database, city, cal.getDays()); + retData.addAll(arrDays); + } + + } catch (JSONException e) { + logger.error("Calendar::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.calendar + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.calendar + "' data model's fields."); + } + logger.debug("Calendar::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("Calendar::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + //check data + if (data.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data not found."); + } + else { + + try { + JSONArray jsArrData = new JSONArray(data); + + if (jsArrData.length() != 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String DayCollectionName = (TimeDataModel.DAYSPECIFICATION+ "_" + city).toLowerCase(); + String CalCollectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + + MongoCollection<Document> dayColl = database.getCollection(DayCollectionName); + MongoCollection<Document> calColl = database.getCollection(CalCollectionName); + + //separate DaySpecification and Calendar objects + Map<String, DaySpecification> mapDaySpecs = new HashMap<>(); + + //we can have only one calendar (with the id) + Calendar cal = null; + + for (int i = 0; i < jsArrData.length(); i++) { + + JSONObject jsData = jsArrData.getJSONObject(i); + String type = jsData.getString("type"); + + if (type != null && type.isEmpty() == false) { + + if (type.compareToIgnoreCase("DaySpecification") == 0) { + DaySpecification day = DaySpecification.createDaySpecification(jsData.toString()); + if (day!= null) { + if (day.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", day.getId()); + o.put("reason", "Wrong input data [DaySpecification]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else + mapDaySpecs.put(day.getId(), day); + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [DaySpecification]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else if (type.compareToIgnoreCase("Calendar") == 0) { + Calendar calData = Calendar.createCalendar(jsData.toString()); + if (calData != null) { + if (calData.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + //check id + if (calData.getId().compareToIgnoreCase(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } else + cal = calData; + } + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [Calendar]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' must be 'Calendar' or 'DaySpecification'"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' not found"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } + + //check if calendar exists: + Document existingCalendar = calColl.find(Filters.eq("_id", id)).first(); + if (existingCalendar == null) { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } else { + + /* Here we have: + * mapDaySpecs: map of valid DaySpecification objects + * cal: data of the calendar object to be updated + * First, we'll check if all the days of the calendar to be updated are in the input data or already present in database + * Next, we'll try to insert/update only the days of the calendar. If there's no error, the calendar itself will be updated + */ + + //check days + List<String> missingDays = new ArrayList<String>(); + List<String> calDays = cal.getDays(); + for (String dayId: calDays) { + //day in input data? + if (mapDaySpecs.containsKey(dayId) == false) { + //not in input data. In database? + if (dayColl.find(Filters.eq("_id", dayId)).first() == null) + missingDays.add(dayId); + //else exists + } //else will be inserted or updated + } + + if (missingDays.size() > 0) { + //some day not found --> error + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]: days " + missingDays + " not found." ); + arrNotInserted.put(o); + } catch (JSONException ex) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON: " + ex.getMessage()); + } + } + else { + //all it's days are present in input data or database --> insert/update them + List<String> errorDays = new ArrayList<String>(); + for (String dayId: calDays) { + DaySpecification daySpec = mapDaySpecs.get(dayId); + if (daySpec != null) { + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(daySpec)); + //need to replace "id" for "_id" + doc.put("_id", daySpec.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", daySpec.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + dayColl.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrInserted.put(o); + mapDaySpecs.remove(dayId); + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = dayColl.find(Filters.eq("_id", daySpec.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + dayColl.replaceOne(Filters.eq("_id", daySpec.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrUpdated.put(o); + mapDaySpecs.remove(dayId); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + } + } + else { + errorDays.add(daySpec.getId()); + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + + } + } + } + } + + for (String dayId : mapDaySpecs.keySet()) { + JSONObject o = new JSONObject(); + try { + o.put("id", dayId); + o.put("reason", "Unreferenced DaySpecification."); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + // errors? + if (errorDays.isEmpty()) { + //update calendar + + try { + + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(cal)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", cal.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existingCalendar.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + + calColl.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + //need to put "id" + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + try { + JSONObject jElem= new JSONObject(sDoc); + o.put("updatedData", jElem); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Some of the days could not be inserted in database"); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + } + + } + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is empty."); + } + + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + logger.debug("Calendar::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "Calendar"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "year", collectionName, "Calendar", Calendar.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + String example = "{\r\n" + + " \"example\": [\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:Calendar:Bilbao:2015\",\r\n" + + " \"type\": \"Calendar\",\r\n" + + " \"city\": \"Bilbao\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -2.93609619140625,\r\n" + + " 43.26345626603949\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"year\": 2015,\r\n" + + " \"days\": [\r\n" + + " \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_01\",\r\n" + + " \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_02\",\r\n" + + " \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_03\"\r\n" + + " ],\r\n" + + " \"createdAt\": \"2021-05-20T09:32:08.809Z\",\r\n" + + " \"modifiedAt\": \"2021-05-20T09:52:04.255Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld2\"\r\n" + + " ]\r\n" + + " },\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_01\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"date\": \"2015-01-01\",\r\n" + + " \"description\": \"AĂƒÂ±o nuevo\",\r\n" + + " \"workingDay\": 0,\r\n" + + " \"schoolDay\": 0,\r\n" + + " \"publicHoliday\": 3,\r\n" + + " \"weekDay\": 4,\r\n" + + " \"createdAt\": \"2021-05-24T13:26:41.828Z\",\r\n" + + " \"modifiedAt\": \"2021-05-25T08:49:11.841Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " },\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_02\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"date\": \"2015-01-02\",\r\n" + + " \"description\": \"\",\r\n" + + " \"workingDay\": 1,\r\n" + + " \"schoolDay\": 1,\r\n" + + " \"publicHoliday\": 0,\r\n" + + " \"weekDay\": 5,\r\n" + + " \"createdAt\": \"2021-05-25T08:26:22.811Z\",\r\n" + + " \"modifiedAt\": \"2021-05-25T08:49:11.946Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " },\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_03\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"date\": \"2015-01-03\",\r\n" + + " \"description\": \"\",\r\n" + + " \"workingDay\": 1,\r\n" + + " \"schoolDay\": 0,\r\n" + + " \"publicHoliday\": 0,\r\n" + + " \"weekDay\": 6,\r\n" + + " \"createdAt\": \"2021-05-25T08:26:22.846Z\",\r\n" + + " \"modifiedAt\": \"2021-05-25T08:49:12.047Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("Calendar::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + + return res; + } + + private List<JSONObject> getDaysOfCalendar(MongoDatabase database, City city, List<String> ids){ + + List<JSONObject> ret = new ArrayList<JSONObject>(); + + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + BasicDBObject inQuery = new BasicDBObject(); + inQuery.put("_id", new BasicDBObject("$in", ids)); + + FindIterable<Document> cursor = coll.find(inQuery); + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + try { + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + ret.add(jElem); + } catch (JSONException e) { + logger.error("DaySpecification::getDataID(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } catch (Exception e) { + logger.error("DaySpecification::getDataID(" + city + "):: Error: " + e.getMessage()); + } + } + + return ret; + + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "Calendar",Calendar.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "Calendar",Calendar.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collectionName, "Calendar"); + + + } + +} + diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CensusObservedController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CensusObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..13f1bcf755ae101a338d07d278ad2b80934540f0 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CensusObservedController.java @@ -0,0 +1,524 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Population.CensusObserved; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class CensusObservedController extends GenericController implements IGenericController{ + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(CensusObservedController.class); + + public CensusObservedController () { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("CensusObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED+ "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> censColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + censColls.add(collectionName); + } + + for (int i = 0; i < arrData.length(); i++) { + CensusObserved record = CensusObserved.createCensusObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("CensusObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (censColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + doc.append("dateObserved", dateObs); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("CensusObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("CensusObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("CensusObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Census Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Census Observation' objects)"); + } + + logger.debug("CensusObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, + List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("CensusObserved::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(CensusObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(CensusObserved.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + //get the different collections between dates + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + List<String> censColls = new ArrayList<String>(); + + censColls = mongoDB.getModelCollectionNamesBetweenYears(collNamePrefix, startDate, endDate, SortingMode.DESC); + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("CensusObserved::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + MongoDatabase database = mongoDB.getDatabase(); + for (String collectionName: censColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("CensusObserved::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.censusObserved + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.censusObserved + "' data model's fields."); + } + logger.debug("CensusObserved::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("CensusObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + CensusObserved record = CensusObserved.createCensusObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year =Utils.getDateYear(dateObs); + String collectionName = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Census Observation' object)"); + } + + logger.debug("CensusObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "CensusObserved"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "CensusObserved", CensusObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:CensusObserved:amsterdam:2016-11-30T07:00:00.00Z\",\r\n" + + " \"type\": \"CensusObserved\",\r\n" + + " \"dateObserved\": \"2021-11-11T07:00:00.00Z\",\r\n" + + " \"createdAt\": \"2021-12-01T13:39:10.00Z\",\r\n" + + " \"modifiedAt\": \"2021-12-01T13:39:10.00Z\",\r\n" + + " \"source\": \"https://ec.europa.eu/eurostat/\",\r\n" + + " \"location\": {\r\n" + + " \"type\": \"Point\",\r\n" + + " \"coordinates\": [\r\n" + + " -4.754444444,\r\n" + + " 41.640833333\r\n" + + " ]\r\n" + + " },\r\n" + + " \"householdID\": 1,\r\n" + + " \"addressRegion\": \"Vienna\",\r\n" + + " \"householdCSWeight\": 0.0,\r\n" + + " \"personalCSWeight\": 0.0,\r\n" + + " \"gender\": \"male\",\r\n" + + " \"currentEconomicStatus\": 1,\r\n" + + " \"nationality\": \"AT\",\r\n" + + " \"employeeCashIncome\": 0.0,\r\n" + + " \"selfEmploymentLosses\": 0.0,\r\n" + + " \"unemploymentBenefits\": 0.0,\r\n" + + " \"oldAgeBenefits\": 0.0,\r\n" + + " \"survivorBenefits\": 0.0,\r\n" + + " \"sicknessBenefits\": 0.0,\r\n" + + " \"disabilityBenefits\": 0.0,\r\n" + + " \"educationAllowances\": 0.0, \r\n" + + " \"hsize\": 2,\r\n" + + " \"age\": 56,\r\n" + + " \"netIncome\": 16999.29,\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org//context.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/census-ngsi.jsonld\"\r\n" + + " ]\r\n" + + "}\r\n" + + ""; + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("CensusObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "CensusObserved",CensusObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "CensusObserved",CensusObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "CensusObserved"); + + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/DaySpecificationController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/DaySpecificationController.java new file mode 100644 index 0000000000000000000000000000000000000000..e431e343c1ad1b8a2bf5cfde70518f918959a3ba --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/DaySpecificationController.java @@ -0,0 +1,515 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Population.CensusObserved; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.DaySpecification; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class DaySpecificationController extends GenericController implements IGenericController{ + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(DaySpecificationController.class); + + + public DaySpecificationController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("DaySpecification::insertData(" + city + ")::IN"); + + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + //check metadata + if (data.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data not found."); + } + else { + + try { + JSONArray jsArrData = new JSONArray(data); + + if (jsArrData.length() != 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + for (int i = 0; i < jsArrData.length(); i++) { + JSONObject jsData = jsArrData.getJSONObject(i); + //DaySpecification daySpec = new DaySpecification(); + //if (daySpec.createFromJSONLD(jsData)) { + DaySpecification daySpec = DaySpecification.createDaySpecification(jsData.toString()); + if (daySpec != null) { + if (daySpec.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", "Wrong input data, some required field(s) missing."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(daySpec)); + //need to replace "id" for "_id" + doc.put("_id", daySpec.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", daySpec.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", daySpec.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + coll.replaceOne(Filters.eq("_id", daySpec.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data, some required field(s) missing."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + /* + JSONObject oIns = new JSONObject(); + JSONObject oNoIns = new JSONObject(); + JSONObject oUpd = new JSONObject(); + try { + oIns.put("inserted", arrInserted); + oNoIns.put("notInserted", arrNotInserted); + oUpd.put("updated", arrUpdated); + lstRes.add(oIns); + lstRes.add(oNoIns); + lstRes.add(oUpd); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + */ + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is empty."); + } + + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + logger.debug("DaySpecification::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("DaySpecification::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(DaySpecification.class, filters)) { + //check the return fields + if (Utils.typeHasFields(DaySpecification.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) { + String strDateStart = dateFormat.format(startDate); + dateRange.put("$gte", strDateStart); + } + if (endDate != null) { + String strDateEnd= dateFormat.format(endDate); + dateRange.put("$lte", strDateEnd); + } + if (dateRange.isEmpty() == false) queryFilters.append("date", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("DaySpecification::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort= new BasicDBObject(); + //querySort.append("date", 1); + querySort.append("date", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and date + if (returnFields.contains("date") == false) returnFields.add("date"); + projection = Projections.fields(Projections.include(returnFields)); + } + + FindIterable<Document> cursor; + if (limit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(limit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("DaySpecification::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.daySpecification + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.daySpecification + "' data model's fields."); + } + + logger.debug("DaySpecification::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("DaySpecification::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + DaySpecification record = DaySpecification.createDaySpecification(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //need to put "id" + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + + try { + JSONObject jElem= new JSONObject(sDoc); + o.put("updatedData", jElem); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + logger.error("DaySpecification::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //no parseable --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Day Specification' object)"); + } + + logger.debug("DaySpecification::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + + return this.getDataByID(city, id, collNamePrefix, "DaySpecification"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "date", collectionName, "DaySpecification", DaySpecification.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example ="{\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_01\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"createdAt\": \"2015-01-01T00:00:00Z\",\r\n" + + " \"modifiedAt\": \"2015-01-01T00:00:00Z\",\r\n" + + " \"date\": \"2015-01-01\",\r\n" + + " \"description\": \"Año nuevo\",\r\n" + + " \"workingDay\": 0,\r\n" + + " \"schoolDay\": 0,\r\n" + + " \"publicHoliday\": 3,\r\n" + + " \"weekDay\": 4,\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " }"; + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("DaySpecification::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "DaySpecification",DaySpecification.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "DaySpecification",DaySpecification.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collectionName, "DaySpecification"); + + } + +} + diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/ElectroMagneticObservedController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/ElectroMagneticObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..852ca75651bccc07b6d198aa8dadf6ab4c38e4f6 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/ElectroMagneticObservedController.java @@ -0,0 +1,426 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.ElectroMagneticObserved; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class ElectroMagneticObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(ElectroMagneticObservedController.class); + + public ElectroMagneticObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("ElectroMagneticObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED+ "_" + city + "_").toLowerCase(); + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + for (int i = 0; i < arrData.length(); i++) { + ElectroMagneticObserved record = ElectroMagneticObserved.createElectroMagneticObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObserved = Utils.ISO2Date(record.getDateObserved()); + + int year = Utils.getDateYear(dateObserved); + + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = collNamePrefix + year; + + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (airColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + + doc.append("dateObserved", dateObserved); + + Date dateObservedTo = Utils.ISO2Date(record.getDateObservedTo()); + if (dateObservedTo != null) + doc.append("dateObservedTo", dateObservedTo); + + Date dateObservedFrom = Utils.ISO2Date(record.getDateObservedFrom()); + if (dateObservedFrom != null) + doc.append("dateObservedFrom", dateObservedFrom); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + if (dateObsFrom != null) + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + if (dateObsTo != null) + doc.append("dateObservedTo", dateObsTo); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'ElectroMagnetic Observed Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Electro Magnetic Observed' objects)"); + } + + logger.debug("ElectroMagneticObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "ElectroMagneticObserved", ElectroMagneticObserved.class); + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("ElectroMagneticObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + ElectroMagneticObserved record = ElectroMagneticObserved.createElectroMagneticObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + + String collectionName = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObserved()); + if (dateObsFrom != null) + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + if (dateObsTo != null) + doc.append("dateObservedTo", dateObsTo); + + //modification date is now + doc.append("dateModified", new Date()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("ElectroMagneticObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "ElectroMagneticObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "ElectroMagneticObserved", ElectroMagneticObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ \r\n" + + " \"id\": \"urn:ngsi-ld:ElectroMagneticObserved:ElectroMagneticObserved:MNCA-EM-018\", \r\n" + + " \"type\": \"ElectroMagneticObserved\", \r\n" + + " \"name\": \"MNCA-EM-018\", \r\n" + + " \"alternateName\": \"AirPort � global Observation\", \r\n" + + " \"description\": \"EMF observation\", \r\n" + + " \"location\": { \r\n" + + " \"type\": \"Point\", \r\n" + + " \"coordinates\": [ \r\n" + + " 43.664810, \r\n" + + " 7.196545 \r\n" + + " ] \r\n" + + " }, \r\n" + + " \"address\": { \r\n" + + " \"addressCountry\": \"FR\", \r\n" + + " \"addressLocality\": \"Nice\", \r\n" + + " \"streetAddress\": \"Terminal 2 - Parking 7\" \r\n" + + " }, \r\n" + + " \"areaServed\": \"Nice Aeroport\", \r\n" + + " \"refDevice\": \"urn:ngsi-ld:Device:NCE-T2-P7-EM03\", \r\n" + + " \"dateObserved\": \"2020-03-17T08:45:00Z\", \r\n" + + " \"eMF\": 950.12, \r\n" + + " \"observedAt\": \"2020-03-17TT08:45:00Z\", \r\n" + + " \"measurementType\": \"Instant\", \r\n" + + " \"measurementInterval\": 1, \r\n" + + " \"reliability\": 0.993 \r\n" + + "} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("ElectroMagneticObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "ElectroMagneticObserved",ElectroMagneticObserved.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "ElectroMagneticObserved",ElectroMagneticObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "ElectroMagneticObserved"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/EventController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/EventController.java new file mode 100644 index 0000000000000000000000000000000000000000..8e5c5ec07bf982e4c9fe98d6e55dcc578aa0223c --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/EventController.java @@ -0,0 +1,556 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Event.Event; +import com.tecnalia.urbanite.storage.DataModel.Event.EventDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.DaySpecification; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class EventController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(EventController.class); + + public EventController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + + @Override + public APIResponse insertData(City city, String data) { + logger.debug("Event::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> evtColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + evtColls.add(collectionName); + } + + for (int i = 0; i < arrData.length(); i++) { + Event record = Event.createEvent(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Event::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + Document doc = null; + try { + if (record.getStartDate() != null) { + Date startDate = Utils.ISO2Date(record.getStartDate()); + + int year = startDate.getYear() + 1900; + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (EventDataModel.EVENT + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "startDate" if the collection is new (not in previous collections read) + if (evtColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("startDate")); + } + } + } + else { + //no date specified --> generic collection + String collectionName = (EventDataModel.EVENT + "_" + city).toLowerCase(); + coll = database.getCollection(collectionName); + } + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + if (record.getStartDate() != null) doc.append("startDate", Utils.ISO2Date(record.getStartDate())); + if (record.getEndDate() != null) doc.append("endDate", Utils.ISO2Date(record.getEndDate())); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform dates (if present) + if (record.getStartDate() != null) doc.append("startDate", Utils.ISO2Date(record.getStartDate())); + if (record.getEndDate() != null) doc.append("endDate", Utils.ISO2Date(record.getEndDate())); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Event::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Event::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Event::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Event' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Event' objects)"); + } + + logger.debug("Event::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + logger.debug("Event::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(Event.class, filters)) { + //check the return fields + if (Utils.typeHasFields(Event.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + + int startYear = 0; + int endYear = 0; + if (startDate != null) startYear = startDate.getYear() + 1900; + if (endDate != null) endYear = endDate.getYear() + 1900; + + //get the different collections between dates + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> evtColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + + try { + int year = Integer.parseInt(collectionName.substring(collNamePrefix.length())); + //no errors --> check if it's in range + boolean addToList = true; + if (startYear > 0 && year < startYear) addToList = false; + if (endYear > 0 && year > endYear) addToList = false; + if (addToList) + evtColls.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR" format --> omit table + } + + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(evtColls); + else + Collections.sort(evtColls, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("startDate", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("Event::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("startDate", -1); //recent first + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and startDate + if (returnFields.contains("startDate") == false) returnFields.add("startDate"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: evtColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("Event::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + EventDataModel.EVENT + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + EventDataModel.EVENT + "' data model's fields."); + } + + logger.debug("Event::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("Event::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + Event record = Event.createEvent(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName; + if (record.getStartDate() != null) { + Date startDate = Utils.ISO2Date(record.getStartDate()); + int year = startDate.getYear() + 1900; + collectionName = (EventDataModel.EVENT + "_" + city + "_" + year).toLowerCase(); + } + else { + //no date specified --> generic collection + collectionName = (EventDataModel.EVENT + "_" + city).toLowerCase(); + } + + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + if (record.getStartDate() != null) doc.append("startDate", Utils.ISO2Date(record.getStartDate())); + if (record.getEndDate() != null) doc.append("endDate", Utils.ISO2Date(record.getEndDate())); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Event' object)"); + } + + logger.debug("Event::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "Event"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "Event", Event.class); + + } + + @Override + public JSONObject getExample() { +JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:event:bilbao:football:270720212355\",\r\n" + + " \"type\": \"Event\",\r\n" + + " \"startDate\": \"2021-10-15T16:30:00\",\r\n" + + " \"endDate\": \"2021-10-15T18:15:00\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " 43.264205, \r\n" + + " -2.949369\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"category\": \"football_match\",\r\n" + + " \"championship\": \"LaLiga\",\r\n" + + " \"homeTeam\": \"Athletic Bilbao\",\r\n" + + " \"guestTeam\": \"Valencia C.F.\", \r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org//context.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/event-ngsi.jsonld\"\r\n" + + " ]\r\n" + + "}\r\n" + + ""; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("Event::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "Event",Event.class); + + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "Event",Event.class); + + } + + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "Event"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GenericController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GenericController.java new file mode 100644 index 0000000000000000000000000000000000000000..6ed0f18fcda6355c00082e74ff9d4d50232fa7d1 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GenericController.java @@ -0,0 +1,551 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonMode; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.client.AggregateIterable; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DB.MongoDBUtils; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class GenericController { + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(GenericController.class); + + public GenericController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + public APIResponse getTDataRangeForDatasetWithYearInNameAtEnd(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort,String dateSortAndRangeField ,String collNamePrefix, String model,Class<?> cls) { + + logger.debug(model+"::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(cls, filters)) { + //check the return fields + if (Utils.typeHasFields(cls, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNamesBetweenYears(collNamePrefix, startDate, endDate, SortingMode.DESC); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + MongoDBUtils.addDateRangeFilter(queryFilters, startDate, endDate, dateSortAndRangeField); + + //fields + MongoDBUtils.addDateJSonFilter(queryFilters, filters); + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + + querySort.append(dateSortAndRangeField, sort.getOrder()); + + //return fields + Bson projection = MongoDBUtils.addReturnFields(returnFields,dateSortAndRangeField); + + int currentLimit = limit; + //search the element in collections + MongoDatabase database = mongoDB.getDatabase(); + for (String collectionName: airColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error(model+"::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + model + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + model + "' data model's fields."); + } + logger.debug(model+"::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort,String sortField ,String collNamePrefix, String model,Class<?> cls) { + logger.debug(model+"::getTData(" + city + ")::IN - Request for data with filtes [" + filters + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + if (Utils.typeHasFieldsJSON(cls, filters)) { + //check the return fields + if (Utils.typeHasFields(cls, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + //Filters + + BasicDBObject queryFilters = new BasicDBObject(); + MongoDBUtils.addDateJSonFilter(queryFilters, filters); + + //Sort + BasicDBObject querySort= new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append(sortField, sort.getOrder()); + + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,sort); + + + //return fields + Bson projection = MongoDBUtils.addReturnFields(returnFields,sortField); + + int currentLimit = limit; + //search the element in collections + for (String collectionName: airColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error(model+"::getTData(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + model + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + model+ "' data model's fields."); + } + logger.debug(model+"::getTData(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse getDataByID(City city, String id,String collNamePrefix, String model) { + + logger.debug(model+"::getDataID(" + city + ")::IN: Request for data with id = " + id); + APIResponse res = new APIResponse(); + + if (id.isEmpty() == false) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + boolean bFound = false; + + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC); + + //search the element in collections + for (String collectionName: airColls) { + MongoCollection<Document> coll = database.getCollection(collectionName); + Document doc = coll.find(new BasicDBObject("_id", id)).first(); + if (doc != null) { + try { + //need to replace "_id" field to "id" + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + JSONObject oDoc = new JSONObject(sDoc); + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + lstRes.add(oDoc); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + bFound = true; + break; + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + } + if (bFound == false) + { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Empty ID."); + } + + logger.debug(model+"::getDataID(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + public APIResponse getDistinct(City city, String[] fields,String collNamePrefix, String model,Class<?> cls) { + logger.debug(model+"::getDistinct(" + city + ")::IN - Request for distinct values of '" + fields + "'"); + + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + try { + if (Utils.typeHasFields(cls, new ArrayList<>(Arrays.asList(fields)))) { + + List<String> trafficCols = new ArrayList<String>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + //get the collection names + trafficCols = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC); + + ArrayList<HashMap<String,Object>> vl = new ArrayList<HashMap<String, Object>>(); + Document fieldDocs = new Document(); + for(String field:fields) { + fieldDocs.append(field, "$"+field); + } + //search the element in collections + for (String collectionName: trafficCols) { + MongoCollection<Document> coll = database.getCollection(collectionName); + try { + + AggregateIterable<Document> docs = coll.aggregate(Arrays.asList( + new Document("$group", new Document("_id", fieldDocs + )))); + + + MongoCursor<?> results = docs.iterator(); + while(results.hasNext()) { + Document doc =(Document) results.next(); + Document docid = (Document) doc.get("_id"); + Set<String> keys = docid.keySet(); + + HashMap<String,Object> map = new HashMap<String, Object>(); + for (String key:keys) { + if (docid.get(key) instanceof Document) { + + map.put(key,new JSONObject(((Document)docid.get(key)).toJson(JsonWriterSettings + .builder() + .outputMode(JsonMode.RELAXED) + .build()))); + } + else { + map.put(key, docid.get(key).toString()); + } + } + + vl.add(map); + + } + } catch (MongoException me) { + logger.error("Can't get disctint values: " + me.getMessage()); + } + } + mongoDB.close(); + try { + JSONObject jsDistinct = new JSONObject(); + jsDistinct.put("values", vl); + retData.add(jsDistinct); + res.setStatus(HttpStatus.OK); + res.setData(retData); + } catch (JSONException e) { + logger.error(model+"::getDistinct:: Error creating JSON with values: " + e.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Error creating JSON with values."); + } + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong field + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields, please check '" + model + "' data model's fields."); + } + + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + logger.debug(model+"::getDistinct(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse getDistinct(City city, String field,String collNamePrefix, String model,Class<?> cls) { + + logger.debug(model+"::getDistinct(" + city + ")::IN - Request for distinct vaules of '" + field + "'"); + + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check the field + if (Utils.typeHasFields(cls, new ArrayList<>(Arrays.asList(field)))) { + + List<String> airColls = new ArrayList<String>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + List<String> lsValues = new ArrayList<String>(); + + //search the element in collections + for (String collectionName: airColls) { + MongoCollection<Document> coll = database.getCollection(collectionName); + try { + DistinctIterable<?> docs = coll.distinct(field, BsonValue.class); + MongoCursor<?> results = docs.iterator(); + while(results.hasNext()) { + Object objRes = Utils.BsonValue2JavaType((BsonValue) results.next()); + String sValue = ""; + if (objRes.getClass() == Date.class) + sValue = Utils.Date2ISO((Date) objRes); + else + sValue = objRes.toString(); + if (lsValues.contains(sValue) == false) lsValues.add(sValue); + } + } catch (MongoException me) { + logger.error("Can't get disctint values: " + me.getMessage()); + } + } + + mongoDB.close(); + + try { + JSONObject jsDistinct = new JSONObject(); + jsDistinct.put("values", lsValues); + retData.add(jsDistinct); + res.setStatus(HttpStatus.OK); + res.setData(retData); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::getDistinct:: Error creating JSON with values: " + e.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Error creating JSON with values."); + } + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong field + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong field '" + field + "', please check '" +model + "' data model's fields."); + } + + logger.debug(model+"::getDistinct(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse deleteDataByID(City city, String id,String collNamePrefix, String model) { + + logger.debug(model+"::deleteDataByID(" + city + ")::IN: Request for data with id = " + id); + APIResponse res = new APIResponse(); + + if (id.isEmpty() == false) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + boolean bFound = false; + List<String> airColls = new ArrayList<String>(); + + //get names of collections, filter by model, and order in "descending" order (recent first) + //String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix, SortingMode.DESC); + + //search the element in collections + for (String collectionName: airColls) { + MongoCollection<Document> coll = database.getCollection(collectionName); + Document doc = coll.find(new BasicDBObject("_id", id)).first(); + if (doc != null) { + try { + DeleteResult result = coll.deleteOne(new Document("_id", id)); + if (result.getDeletedCount() != 0) { + //deleted + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("deleted", id); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + bFound = true; + break; + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + } + if (bFound == false) + { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Empty ID."); + } + + logger.debug(model+"::deleteDataByID(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GtfsShapeController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GtfsShapeController.java new file mode 100644 index 0000000000000000000000000000000000000000..e1340eb17618efd1d57efa95d8b6ddbb416ed37c --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GtfsShapeController.java @@ -0,0 +1,489 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShape; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShapedataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class GtfsShapeController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(GtfsShapeController.class); + + public GtfsShapeController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("GtfsShape::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (GtfsShapedataModel.GTFSSHAPE+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + GtfsShape record = GtfsShape.createGtfsShape(arrData.getString(i)); + if (record == null || record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("GtfsShape::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + Date dateObs = null; + if (record.getDateObserved() != null && !record.getDateObserved().isEmpty() && record.getDateObserved().compareToIgnoreCase("null") != 0) { + dateObs= Utils.ISO2Date(record.getDateObserved()); + } + + else { + dateObs = d; + } + doc.append("dateObserved", dateObs); + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + Date dateObs = null; + if (record.getDateObserved() != null && !record.getDateObserved().isEmpty() && record.getDateObserved().compareToIgnoreCase("null") != 0) { + dateObs= Utils.ISO2Date(record.getDateObserved()); + } + + else { + dateObs = new Date(); + } + doc.append("dateObserved", dateObs); + + + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("GtfsShape::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("GtfsShape::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("GtfsShape::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + logger.debug("GtfsShape::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("GtfsShape::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(GtfsShape.class, filters)) { + //check the return fields + if (Utils.typeHasFields(GtfsShape.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + MongoCollection<Document> coll = null; + String CollectionName = (GtfsShapedataModel.GTFSSHAPE+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("GtfsShape::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateModified", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("GtfsShape::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.gtfsShape + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.gtfsShape + "' data model's fields."); + } + logger.debug("GtfsShape::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("GtfsShape::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + GtfsShape record = GtfsShape.createGtfsShape(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + Date dateObs = null; + if (record.getDateObserved() != null && !record.getDateObserved().isEmpty() && record.getDateObserved().compareToIgnoreCase("null") != 0) { + dateObs= Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + } + + else { + doc.append("dateObserved", existing.get("dateCreated")); + } + + + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('GtfsShape' object)"); + } + + logger.debug("GtfsShape::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "GtfsShape"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "GtfsShape", GtfsShape.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"id\": \"urn:ngsi-ld:PointOfInterest:\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -3.712247222222222,\r\n" + + " 40.423852777777775\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"source\": \"http://datos.madrid.es\",\r\n" + + " \"type\": \"GtfsShape\"\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("GtfsShape::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "GtfsShape",GtfsShape.class); + + + } + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "GtfsShape",GtfsShape.class); + + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "GtfsShape"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IAggregatorController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IAggregatorController.java new file mode 100644 index 0000000000000000000000000000000000000000..c51575ec51f65ad883b90ae413c7a057fcfad7db --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IAggregatorController.java @@ -0,0 +1,30 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.Response; +import org.springframework.stereotype.Controller; + +import java.util.Date; +import java.util.Map; + +@Controller +public interface IAggregatorController +{ + Response aggregate(City city, String metric, Date start, Date end, AggregatorEnum aggregator, String downsample, Map<String,String> tags); +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IGenericController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IGenericController.java new file mode 100644 index 0000000000000000000000000000000000000000..f4a7f934f64036e3cbb8c475ea1709959cbc9f1d --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IGenericController.java @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.Date; +import java.util.List; + +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; + +public interface IGenericController { + + public APIResponse insertData(City city, String data); + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort); + public APIResponse updateData(City city, String id, String data); + public APIResponse getDataByID(City city, String id); + public APIResponse getTData (City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort); + public JSONObject getExample(); + public APIResponse getDistinct(City city, String field); + public APIResponse getDistinct(City city, String[] fields); + public APIResponse deleteDataByID(City city, String id); + + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MapLayerController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MapLayerController.java new file mode 100644 index 0000000000000000000000000000000000000000..7201163243416c8dcfaece4adc6fa3be03715f6a --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MapLayerController.java @@ -0,0 +1,459 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Map.MapLayer; +import com.tecnalia.urbanite.storage.DataModel.Map.MapLayerDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class MapLayerController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(MapLayerController.class); + + public MapLayerController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("MapLayer::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + MongoCollection<Document> coll = null; + + String CollectionName = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + MapLayer record = MapLayer.createMapLayer(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("MapLayer::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + doc.remove("map"); + DBObject json = (DBObject)BasicDBObject.parse(record.getMap().toString());//(BasicDBObject) JSON.parse(record.getMap().toString()); + doc.put("map",json); + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("MapLayer::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("MapLayer::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("MapLayer::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'MapLayer Observed ' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'MapLayer' objects)"); + } + + logger.debug("MapLayer::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + logger.debug("MapLayer::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(MapLayer.class, filters)) { + //check the return fields + if (Utils.typeHasFields(MapLayer.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + coll = database.getCollection(CollectionName); + + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateModified", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("MapLayer::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + + querySort.append("dateCreated", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + } catch (JSONException e) { + logger.error("TransportStation::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.transportStation + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.transportStation + "' data model's fields."); + } + + logger.debug("MapLayer::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("MapLayer::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + MapLayer record = MapLayer.createMapLayer(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + + String collectionName = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("MapLayer::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "MapLayer"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateCreated", collNamePrefix, "MapLayer", MapLayer.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("MapLayer::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "MapLayer",MapLayer.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "MapLayer",MapLayer.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "MapLayer"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MetadataController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MetadataController.java new file mode 100644 index 0000000000000000000000000000000000000000..76a1c16706086a35d2fbf2b0f1299801148467f2 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MetadataController.java @@ -0,0 +1,547 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.Common.CommonDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class MetadataController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(MetadataController.class); + + public MetadataController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + public APIResponse insertMetadata(String id, String metadata ) { + + logger.debug("Metadata::insertMetadata(" + id + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + //check metadata + if (metadata.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input metadata not found."); + } + else { + try { + JSONObject jsMeta= new JSONObject(metadata); + //metadata in JSON format + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document doc = null; + try { + doc = Document.parse(jsMeta.toString()); + doc.put("_id", id); + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", id); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + String sExisting = existing.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + + //we have to pre-process some fields, to avoid duplicates: accessRights, foaf:Organization, modified, description and title + //1. Store the values of the new metadata, and delete them + String sMetaAccess = ""; + String sMetaModif = ""; + String sMetaOrgName = ""; + String sMetaOrgHomepage = ""; + + JSONArray jsArrMetaDesc = new JSONArray(); + JSONArray jsArrMetaTitle = new JSONArray(); + JSONArray jsArrNewMetaGraph = new JSONArray(); + + JSONArray jsArrMetaGraph = jsMeta.getJSONArray("@graph"); + for (int i = 0; i < jsArrMetaGraph.length(); i++) { + boolean bAddToGraph = true; + JSONObject o = jsArrMetaGraph.getJSONObject(i); + if (o.has("@type")) { + String type = o.getString("@type"); + //accessRights + if (type.compareTo("accessRights") == 0) { + if (o.has("label")) sMetaAccess = o.getString("label"); + bAddToGraph = false; + } + //foaf:Organization + else if (type.compareTo("foaf:Organization") == 0) { + if (o.has("name")) sMetaOrgName = o.getString("name"); + if (o.has("homepage")) sMetaOrgHomepage= o.getString("homepage"); + bAddToGraph = false; + } + //Dataset + else if (type.compareTo("dcat:Dataset") == 0) { + //modified field --> store and remove + if (o.has("modified")) { + sMetaModif = o.getString("modified"); + o.remove("modified"); + } + //description field --> store as an Array and remove + if (o.has("description")) { + Object objDesc = o.get("description"); + if (objDesc instanceof JSONArray) jsArrMetaDesc = o.getJSONArray("description"); + else if (objDesc instanceof JSONObject) jsArrMetaDesc.put(o.getJSONObject("description")); + o.remove("description"); + } + //title field --> store as an Array and remove + if (o.has("title")) { + Object objTitle = o.get("title"); + if (objTitle instanceof JSONArray) jsArrMetaTitle = o.getJSONArray("title"); + else if (objTitle instanceof JSONObject) jsArrMetaTitle.put(o.getJSONObject("title")); + o.remove("title"); + } + //remove other fields that will be updated: accessRights and publisher + if (o.has("accessRights")) o.remove("accessRights"); + if (o.has("publisher")) o.remove("publisher"); + } + } + if (bAddToGraph) jsArrNewMetaGraph.put(o); + } + + //set the new @graph + jsMeta.remove("@graph"); + jsMeta.put("@graph", jsArrNewMetaGraph); + + + //2. Update the previous existing metadata + JSONObject jsExisting = new JSONObject(sExisting); + JSONArray jsArrNewExistingGraph = new JSONArray(); + + JSONArray jsArrExistingGraph = jsExisting.getJSONArray("@graph"); + for (int i = 0; i < jsArrExistingGraph.length(); i++) { + JSONObject o = jsArrExistingGraph.getJSONObject(i); + if (o.has("@type")) { + String type = o.getString("@type"); + //accessRights + if (type.compareTo("accessRights") == 0) { + if (o.has("label")) { + if (sMetaAccess.isEmpty() == false) + o.put("label", sMetaAccess); + } + } + //foaf:Organization + else if (type.compareTo("foaf:Organization") == 0) { + if (o.has("name")) + if (sMetaOrgName.isEmpty() == false) + o.put("name", sMetaOrgName); + if (o.has("homepage")) + if (sMetaOrgHomepage.isEmpty() == false) + o.put("homepage", sMetaOrgHomepage); + } + //Dataset + else if (type.compareTo("dcat:Dataset") == 0) { + //modified + if (sMetaModif.isEmpty() == false) + o.put("modified", sMetaModif); + //description: the existing values must be updated (if changed) and the new values, added (if any) + if (o.has("description")) { + JSONArray jsArrExistingDesc = new JSONArray(); + Object objDesc = o.get("description"); + if (objDesc instanceof JSONArray) jsArrExistingDesc = o.getJSONArray("description"); + else if (objDesc instanceof JSONObject) jsArrExistingDesc.put(o.getJSONObject("description")); + JSONArray jsArrNewDesc = JoinJSONArraysByField(jsArrExistingDesc, jsArrMetaDesc, "@language"); + if (jsArrNewDesc.length() == 1) + o.put("description", jsArrNewDesc.get(0)); + else + o.put("description", jsArrNewDesc); + } + //title: the existing values must be updated (if changed) and the new values, added (if any) + if (o.has("title")) { + JSONArray jsArrExistingTitle = new JSONArray(); + Object objTitle = o.get("title"); + if (objTitle instanceof JSONArray) jsArrExistingTitle = o.getJSONArray("title"); + else if (objTitle instanceof JSONObject) jsArrExistingTitle.put(o.getJSONObject("title")); + JSONArray jsArrNewTitle = JoinJSONArraysByField(jsArrExistingTitle, jsArrMetaTitle, "@language"); + if (jsArrNewTitle.length() == 1) + o.put("title", jsArrNewTitle.get(0)); + else + o.put("title", jsArrNewTitle); + } + } + } + + jsArrNewExistingGraph.put(o); + } + + //set the new @graph + jsExisting.remove("@graph"); + jsExisting.put("@graph", jsArrNewExistingGraph); + + + //3. Add the two models + String finalMetadata = Utils.addJsonLdModels(jsExisting.toString(), jsMeta.toString()); + + try { + Document finalDoc = Document.parse(finalMetadata); + coll.replaceOne(Filters.eq("_id", id),finalDoc); + JSONObject o = new JSONObject(); + o.put("id", id); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", id); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Metadata::insertMetadata(" + id + "). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", id); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Metadata::insertMetadata(" + id + "). Error creating JSON: " + e.getMessage()); + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Metadata::insertMetadata(" + id + "). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + catch (JSONException ex) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input metadata is not in JSON format"); + } + } + + logger.debug("Metadata::insertMetadata(" + id + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + public APIResponse getDataset(String id) { + + logger.debug("Metadata::getDataset(" + id + ")::IN"); + APIResponse res = new APIResponse(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document doc = coll.find(new BasicDBObject("_id", id)).first(); + if (doc != null) { + try { + doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + JSONObject oDoc = new JSONObject(sDoc); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + lstRes.add(oDoc); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else + { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::getDataset(" + id + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + public APIResponse deleteMetadata(String id) { + + logger.debug("Metadata::deleteMetadata(" + id + ")::IN"); + APIResponse res = new APIResponse(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + DeleteResult result = coll.deleteOne(new Document("_id", id)); + if (result.getDeletedCount() == 0) { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Dataset '" + id + "' not found."); + } else { + //deleted + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("deleted", id); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::deleteMetadata(" + id + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + public APIResponse getCatalogueDatasets() { + logger.debug("Metadata::getCatalogueDatasets::IN"); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + //Sort? + //BasicDBObject querySort= new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + + //FindIterable<Document> cursor = coll.find().sort(querySort); + FindIterable<Document> cursor = coll.find(); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + String id = doc.get("_id").toString(); + doc.remove("_id"); + String sMeta = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + try { + JSONObject jMeta = new JSONObject(); + jMeta.put("id", id); + jMeta.put("metadata", new JSONObject(sMeta)); + retData.add(jMeta); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::getCatalogueDatasets::OUT [" + res.getStatus() + "]"); + return res; + } + + + //search example (mongodb): + //1. "text" index creation: + //db.getCollection('metadata').createIndex( { "$**": "text" } ) + //2. search for a value in any text field + //db.getCollection('metadata').find({$text: {$search: "Bilbao"}}) + + public APIResponse searchDatasets(String tags) { + logger.debug("Metadata::searchDatasets::IN"); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + + /* we'll search each value of the array in fields title, description and keywords --> OR OPERATION + * all of them must appear --> AND OPERATION + * i.e: search for "a" and "b" --> + * [title = "a" OR description = "a" OR keywords = "a"] AND [title = "b" OR description = "b" OR keywords = "b"] + * NOTE: we have several title and description fields: + * @graph.title.value (for datasets) + * @graph.title (for distributions) + * We need to search in all of them, because we can have somethin like: + * dataset "calendar bilbao" + * distribution "calendar bilbao 2016" + * If we search for 2016 only in dataset, it won't be foud, but it must + */ + BasicDBObject andQuery = new BasicDBObject(); + List<BasicDBObject> andLst = new ArrayList<BasicDBObject>(); + String[] tagList = tags.split(" "); + for (String tag: tagList) { + BasicDBObject orQuery = new BasicDBObject(); + List<BasicDBObject> orLst= new ArrayList<BasicDBObject>(); + //datasets: + BasicDBObject clauseTitleVal = new BasicDBObject("@graph.title.@value", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + BasicDBObject clauseDescVal = new BasicDBObject("@graph.description.@value", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + BasicDBObject clauseKeyword = new BasicDBObject("@graph.keyword", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + //distributions + BasicDBObject clauseTitle = new BasicDBObject("@graph.title", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + BasicDBObject clauseDesc = new BasicDBObject("@graph.description", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + orLst.add(clauseTitle); + orLst.add(clauseDesc); + orLst.add(clauseTitleVal); + orLst.add(clauseDescVal); + orLst.add(clauseKeyword); + orQuery.put("$or", orLst); + andLst.add(orQuery); + } + andQuery.put("$and", andLst); + + + FindIterable<Document> cursor = coll.find(andQuery); + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + String id = doc.get("_id").toString(); + doc.remove("_id"); + String sMeta = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + try { + JSONObject jMeta = new JSONObject(); + jMeta.put("id", id); + jMeta.put("metadata", new JSONObject(sMeta)); + retData.add(jMeta); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::searchDatasets::OUT [" + res.getStatus() + "]"); + return res; + } + + + private JSONArray JoinJSONArraysByField(JSONArray originalJSArray, JSONArray newJSArray, String fieldName) { + + Map<String, JSONObject> mpData = new HashMap<>(); + + try { + for (int origIndx = 0; origIndx < originalJSArray.length(); origIndx++) { + JSONObject obj = originalJSArray.getJSONObject(origIndx); + if (obj.has(fieldName)) + mpData.put(obj.getString(fieldName), obj); + } + + for (int newIndx = 0; newIndx < newJSArray.length(); newIndx++) { + JSONObject obj = newJSArray.getJSONObject(newIndx); + if (obj.has(fieldName)) + mpData.put(obj.getString(fieldName), obj); + } + + JSONArray jsRet = new JSONArray(); + for (String key : mpData.keySet()) + jsRet.put(mpData.get(key)); + + return jsRet; + } + catch (JSONException e) { + System.err.println ("Error Joinning JSONArrays by fields: " + e.getMessage()); + return originalJSArray; + } + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/NoiseLevelObservedController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/NoiseLevelObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..3fb117590378d639e4b5052736bd8ab1301d788e --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/NoiseLevelObservedController.java @@ -0,0 +1,408 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.DataModel.Environment.NoiseLevelObserved; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class NoiseLevelObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(NoiseLevelObservedController.class); + + public NoiseLevelObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("NoiseLevelObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + for (int i = 0; i < arrData.length(); i++) { + NoiseLevelObserved record = NoiseLevelObserved.createNoiseLevelObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObservedFrom = Utils.ISO2Date(record.getDateObservedFrom()); + + int year = Utils.getDateYear(dateObservedFrom); + + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = collNamePrefix + year; + + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (airColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObservedFrom")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + Date dateObservedTo = Utils.ISO2Date(record.getDateObservedTo()); + doc.append("dateObservedTo", dateObservedTo); + + doc.append("dateObservedFrom", dateObservedFrom); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + doc.append("dateObservedTo", dateObsTo); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Noise Level Observed Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Noise Level Observed' objects)"); + } + + logger.debug("NoiseLevelObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateObservedFrom", collNamePrefix, "NoiseLevelObserved", NoiseLevelObserved.class); + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("NoiseLevelObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + NoiseLevelObserved record = NoiseLevelObserved.createNoiseLevelObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + int year = Utils.getDateYear(dateObsFrom); + + String collectionName = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + + //Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + doc.append("dateObservedTo", dateObsTo); + + //modification date is now + doc.append("dateModified", new Date()); + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("NoiseLevelObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "NoiseLevelObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObservedFrom", collNamePrefix, "NoiseLevelObserved", NoiseLevelObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ \r\n" + + " \"id\": \"Vitoria-NoiseLevelObserved-2016-12-28T11:00:00_2016-12-28T12:00:00\", \r\n" + + " \"type\": \"NoiseLevelObserved\", \r\n" + + " \"LAS\": 91.6, \r\n" + + " \"LAeq\": 67.8, \r\n" + + " \"LAeq_d\": 65.4, \r\n" + + " \"LAmax\": 94.5, \r\n" + + " \"dateObservedFrom\": \"2016-12-28T11:00:00.00Z\", \r\n" + + " \"dateObservedTo\": \"2016-12-28T12:00:00.00Z\", \r\n" + + " \"location\": { \r\n" + + " \"type\": \"Point\", \r\n" + + " \"coordinates\": [-2.698, 42.8491] \r\n" + + " } \r\n" + + "} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "NoiseLevelObserved",NoiseLevelObserved.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "NoiseLevelObserved",NoiseLevelObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "NoiseLevelObserved"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/OriginDestinationMatrixController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/OriginDestinationMatrixController.java new file mode 100644 index 0000000000000000000000000000000000000000..ea65c5faf4987bdbe257871384b6ce419f1652b2 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/OriginDestinationMatrixController.java @@ -0,0 +1,556 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShape; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShapedataModel; +import com.tecnalia.urbanite.storage.DataModel.Transportation.OriginDestinationMatrix; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class OriginDestinationMatrixController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(OriginDestinationMatrixController.class); + + public OriginDestinationMatrixController () { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("OriginDestinationMatrix::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String CollectionName = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + coll = database.getCollection(CollectionName); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> odCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(CollectionName)) + odCols.add(collectionName); + } + + + for (int i = 0; i < arrData.length(); i++) { + OriginDestinationMatrix record = OriginDestinationMatrix.createOriginDestinationMatrix(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Origin Destination Matrix' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Origin Destination Matrix' objects)"); + } + + logger.debug("OriginDestinationMatrix::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, + List<String> returnFields, int limit, SortingMode sort) { + logger.debug("OriginDestinationMatrix::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(OriginDestinationMatrix.class, filters)) { + //check the return fields + if (Utils.typeHasFields(OriginDestinationMatrix.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city).toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> odCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + odCols.add(collectionName.toLowerCase()); + break; + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(odCols); + else + Collections.sort(odCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + ////dates + //BasicDBObject dateRange = new BasicDBObject (); + //if (startDate != null) dateRange.put("$gte", startDate); + //if (endDate != null) dateRange.put("$lte", endDate); + //if (dateRange.isEmpty() == false) queryFilters.append("startDate", dateRange); + + //dates and hours + DateFormat dFDate = new SimpleDateFormat("YYYY-MM-dd"); + DateFormat dFHour = new SimpleDateFormat("HH:mm:ss"); + dFDate.setTimeZone(TimeZone.getTimeZone("UTC")); + dFHour.setTimeZone(TimeZone.getTimeZone("UTC")); + if (startDate != null) { + queryFilters.append("startDate", new BasicDBObject("$gte", dFDate.format(startDate))); + queryFilters.append("startPeriod", new BasicDBObject("$gte", dFHour.format(startDate))); + } + if (endDate != null) { + queryFilters.append("endDate", new BasicDBObject("$lte", dFDate.format(endDate))); + queryFilters.append("endPeriod", new BasicDBObject("$lte", dFHour.format(endDate))); + } + + + + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and startDate + if (returnFields.contains("startDate") == false) returnFields.add("startDate"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: odCols) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.originDestinationMatrix + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.originDestinationMatrix + "' data model's fields."); + } + + logger.debug("OriginDestinationMatrix::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("OriginDestinationMatrix::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + OriginDestinationMatrix record = OriginDestinationMatrix.createOriginDestinationMatrix(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //o.put("updatedData", doc); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse--> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Origin Destination Matrix' object)"); + } + + logger.debug("OriginDestinationMatrix::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "OriginDestinationMatrix"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "OriginDestinationMatrix", OriginDestinationMatrix.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:odm:bilbao:wifi:daily:270720212355\",\r\n" + + " \"aggregationType\": \"daily\",\r\n" + + " \"type\": \"OriginDestinationMatrix\",\r\n" + + " \"category\": \"wifi\",\r\n" + + " \"endDate\": \"2021-09-30\",\r\n" + + " \"endPeriod\": \"23:59:00\",\r\n" + + " \"matrixData\": [\r\n" + + " {\r\n" + + " \"arrivesTo\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"departsFrom\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"time\": 38,\r\n" + + " \"volume\": 1246.0,\r\n" + + " \"volumePercentage\": 3.15\r\n" + + " },\r\n" + + " {\r\n" + + " \"arrivesTo\": \"BASURTO\",\r\n" + + " \"departsFrom\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"time\": 33,\r\n" + + " \"volume\": 3581.0,\r\n" + + " \"volumePercentage\": 9.05\r\n" + + " },\r\n" + + " {\r\n" + + " \"arrivesTo\": \"CASCO VIEJO\",\r\n" + + " \"departsFrom\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"time\": 66,\r\n" + + " \"volume\": 899.0,\r\n" + + " \"volumePercentage\": 2.27\r\n" + + " }\r\n" + + " ],\r\n" + + " \"startDate\": \"2021-09-01\",\r\n" + + " \"startPeriod\": \"00:00:00\",\r\n" + + " \"travelMode\": \"all\",\r\n" + + " \"zones\": \"Bilbao_zones\",\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org//context.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/ODMatrix-ngsi.jsonld\"\r\n" + + " ]\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "OriginDestinationMatrix",OriginDestinationMatrix.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "OriginDestinationMatrix",OriginDestinationMatrix.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "OriginDestinationMatrix"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PointOfInterestController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PointOfInterestController.java new file mode 100644 index 0000000000000000000000000000000000000000..e69e286123e26ecabd5e81fa874f0489b22fb28c --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PointOfInterestController.java @@ -0,0 +1,475 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterest; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterestDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.DataModel.Transportation.OriginDestinationMatrix; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class PointOfInterestController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(PointOfInterestController.class); + + public PointOfInterestController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("PointOfInterest::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (PointOfInterestDataModel.POINTOFINTEREST+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + PointOfInterest record = PointOfInterest.createPointOfInterest(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PointOfInterest' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PointOfInterest' objects)"); + } + + logger.debug("PointOfInterest::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("PointOfInterest::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(PointOfInterest.class, filters)) { + //check the return fields + if (Utils.typeHasFields(PointOfInterest.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (PointOfInterestDataModel.POINTOFINTEREST+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateModified", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("PointOfInterest::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateModified", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateModified") == false) returnFields.add("dateModified"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + } catch (JSONException e) { + logger.error("PointOfInterest::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.pointOfInterest + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.pointOfInterest + "' data model's fields."); + } + logger.debug("PointOfInterest::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("PointOfInterest::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + PointOfInterest record = PointOfInterest.createPointOfInterest(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('PointOfInterest' object)"); + } + + logger.debug("PointOfInterest::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "PointOfInterest"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "category", collNamePrefix, "PointOfInterest", PointOfInterest.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"address\": {\r\n" + + " \"addressCountry\": \"ES\",\r\n" + + " \"addressLocality\": \"Madrid\",\r\n" + + " \"streetAddress\": \"Plaza de Espa\\u00f1a\",\r\n" + + " \"type\": \"PostalAddress\"\r\n" + + " },\r\n" + + " \"areaServed\": \"Brooklands\",\r\n" + + " \"id\": \"urn:ngsi-ld:PointOfInterest:\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -3.712247222222222,\r\n" + + " 40.423852777777775\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"source\": \"http://datos.madrid.es\",\r\n" + + " \"type\": \"PointOfInterest\"\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("PointOfInterest::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "PointOfInterest",PointOfInterest.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "PointOfInterest",PointOfInterest.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "PointOfInterest"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PopulationObservedController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PopulationObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..6f386bb488a06235670e1f2b3962248f69f620a3 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PopulationObservedController.java @@ -0,0 +1,373 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.google.gson.FieldNamingPolicy; +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationDataModel; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationObserved; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class PopulationObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(PopulationObservedController.class); + + public PopulationObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("PopulationObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + for (int i = 0; i < arrData.length(); i++) { + PopulationObserved record = PopulationObserved.createPopulationObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + Date now = new Date(); + int year = Utils.getDateYear(now); + + + doc = Document.parse(Utils.Object2JSON(record,FieldNamingPolicy.LOWER_CASE_WITH_DASHES)); + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) { + doc.append("dateObserved", dateObs); + + year = Utils.getDateYear(dateObs); + } + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = collNamePrefix + year; + + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + } + + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + + doc.append("dateCreated", now); + doc.append("dateModified", now); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PopulationObserved Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PopulationObserved' objects)"); + } + + logger.debug("PopulationObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateModified", collNamePrefix, "NoiseLevelObserved", PopulationObserved.class); + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("PopulationObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + PopulationObserved record = PopulationObserved.createPopulationObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + Date now = new Date(); + int year = Utils.getDateYear(now); + + MongoDatabase database = mongoDB.getDatabase(); + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) { + year = Utils.getDateYear(dateObs); + } + String collectionName = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + if (dateObs != null) + doc.append("dateObserved", dateObs); + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("PopulationObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "PopulationObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateModified", collNamePrefix, "PopulationObserved", PopulationObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ } "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("PopulationObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "PopulationObserved",PopulationObserved.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "PopulationObserved",PopulationObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "PopulationObserved"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TouristTripController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TouristTripController.java new file mode 100644 index 0000000000000000000000000000000000000000..7002c418babd346081caa850d628fcd212e6866f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TouristTripController.java @@ -0,0 +1,821 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterest; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterestDataModel; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTrip; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTripDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class TouristTripController extends GenericController implements IGenericController { + + private Logger logger = LoggerFactory.getLogger(TouristTripController.class); + private DBConfiguration.DBParams dbParams; + + + private MongoDBManager mongoDBGlobal = null; + + + + public TouristTripController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + this.mongoDBGlobal = new MongoDBManager(dbParams); + this.mongoDBGlobal.connect(); + } + + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("TouristTrip::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> touristCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + touristCols.add(collectionName); + } + + + for (int i = 0; i < arrData.length(); i++) { + TouristTrip record = TouristTrip.createTouristTrip(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("TouristTrip::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getStartDate()); + int year = dateObs.getYear() + 1900; + int month = dateObs.getMonth() + 1; + String yearMonth = year + "_" + (month<10?"0":"") + month; + if (collectionList.containsKey(yearMonth)) + coll = collectionList.get(yearMonth); + else { + String collectionName = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_" + yearMonth).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(yearMonth, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (touristCols.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("startDate")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + + Date startDate = null; + if (record.getStartDate() != null) { + if (record.getStartDate().isEmpty() == false && record.getStartDate().compareToIgnoreCase("null") != 0) + startDate= Utils.ISO2Date(record.getStartDate()); + } + if (startDate != null) doc.append("startDate", startDate); + + Date endDate = null; + if (record.getEndDate() != null) { + if (record.getEndDate().isEmpty() == false && record.getEndDate().compareToIgnoreCase("null") != 0) + endDate= Utils.ISO2Date(record.getEndDate()); + } + if (endDate != null) doc.append("endDate", endDate); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + Date startDate = null; + if (record.getStartDate() != null) { + if (record.getStartDate().isEmpty() == false && record.getStartDate().compareToIgnoreCase("null") != 0) + startDate= Utils.ISO2Date(record.getStartDate()); + } + if (startDate != null) doc.append("startDate", startDate); + + + Date endDate = null; + if (record.getEndDate() != null) { + if (record.getEndDate().isEmpty() == false && record.getEndDate().compareToIgnoreCase("null") != 0) + endDate= Utils.ISO2Date(record.getEndDate()); + } + if (endDate != null) doc.append("endDate", endDate); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TouristTrip::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TouristTrip::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("TouristTrip::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TouristTrip' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TouristTrip' objects)"); + } + + logger.debug("TouristTrip::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + public APIResponse getTDataRangeOtherMethod(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TouristTrip::getTDataRangeOtherMethod(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TouristTrip.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TouristTrip.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRangeOtherMethod(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + + logger.info("TouristTrip::getTDataRangeOtherMethod::Antes del For"); + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + + List<Document> results = new ArrayList<>(); + cursor.into(results); + + allResults.addAll(results); + + int resSize = results.size(); + + //reached limit? + int total = allResults.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + + } + logger.info("TouristTrip::getTDataRangeOtherMethod::Despues del For"); + + logger.info("TouristTrip::getTDataRangeOtherMethod::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRangeOtherMethod(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TouristTrip::getTDataRangeOtherMethod::Despues de procesamiento"); + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.touristTrip + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.touristTrip + "' data model's fields."); + } + + + logger.debug("TouristTrip::getTDataRangeOtherMethod(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TouristTrip::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TouristTrip.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TouristTrip.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.touristTrip + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.touristTrip + "' data model's fields."); + } + + logger.debug("TouristTrip::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("TouristTrip::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + try { + TouristTrip record = TouristTrip.createTouristTrip(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getStartDate()); + int year = dateObs.getYear() + 1900; + int month = dateObs.getMonth() + 1; + String yearMonth = year + "_" + (month<10?"0":"") + month; + String collectionName = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_" + yearMonth).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + Date startDate = null; + if (record.getStartDate() != null) { + if (record.getStartDate().isEmpty() == false && record.getStartDate().compareToIgnoreCase("null") != 0) + startDate= Utils.ISO2Date(record.getEndDate()); + } + if (startDate != null) doc.append("startDate", startDate); + + + Date endDate = null; + if (record.getEndDate() != null) { + if (record.getEndDate().isEmpty() == false && record.getEndDate().compareToIgnoreCase("null") != 0) + endDate= Utils.ISO2Date(record.getEndDate()); + } + if (endDate != null) doc.append("endDate", endDate); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //o.put("updatedData", doc); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse--> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('TouristTrip' object)"); + } + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + logger.debug("TouristTrip::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "TouristTrip"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "TouristTrip", TouristTrip.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("TouristTrip::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "TouristTrip",TouristTrip.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "TouristTrip",TouristTrip.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "TouristTrip"); + + } + +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TrafficFlowObservedController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TrafficFlowObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..ae4d3e10a2d2c0d88c598319ffbe736fead3219f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TrafficFlowObservedController.java @@ -0,0 +1,1516 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import com.google.gson.JsonParser; +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.*; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.opentsdb.client.OpentsdbClient; +import com.tecnalia.opentsdb.client.PointBuilder; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTrip; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TrafficFlowObserved; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; +import com.tecnalia.urbanite.storage.controllers.models.DatabaseAction; +import com.tecnalia.urbanite.storage.controllers.models.enums.DatabaseActionTypeEnum; +import com.tecnalia.urbanite.storage.exception.DocumentNotFoundException; +import com.tecnalia.urbanite.storage.exception.MongoDbConnectionException; +import com.tecnalia.urbanite.storage.exception.OpenTsdbOperationException; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; + +@Component +public class TrafficFlowObservedController extends GenericController implements IGenericController { + + + private final Logger logger = LoggerFactory.getLogger(TrafficFlowObservedController.class); + private DBConfiguration.DBParams dbParams; + private MongoDBManager mongoDBGlobal = null; + + public TrafficFlowObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + this.mongoDBGlobal = new MongoDBManager(dbParams); + this.mongoDBGlobal.connect(); + } + + @Override + public APIResponse insertData(City city, String data) + { + logger.debug("TrafficFlowObserved::insertData(" + city + ")::IN"); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try + { + JSONArray dataArray = new JSONArray(data); + + if ( dataArray.length() <= 0 ) + { + logger.debug("TrafficFlowObserved::insertData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Input data is not in required format (list of 'Traffic Flow Observation' objects)"); + } + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + + if ( !mongoDB.connect()) + { + logger.debug("TrafficFlowObserved::insertData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR,"Can't connect to database."); + } + + MongoDatabase database = mongoDB.getDatabase(); + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + + for (String collectionName: colNames) + { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + { + trafficCols.add(collectionName); + } + } + + for (int row = 0; row < dataArray.length(); row++) + { + save( TrafficFlowObserved.createTrafficFlowObserved(dataArray.getString(row)), + collectionList, trafficCols, database, city, arrInserted, arrUpdated, arrNotInserted); + } + + } catch (JSONException ex) + { + logger.error(ex.getMessage(), ex); + return new APIResponse(HttpStatus.BAD_REQUEST, "Input data is not in required format (list of 'Traffic Flow Observation' objects)"); + } + + logger.debug("TrafficFlowObserved::insertData(" + city + ")::OUT [" + HttpStatus.OK + "]: " + + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + + return new APIResponse(HttpStatus.OK, buildResponse(arrInserted, arrNotInserted, arrUpdated, city)); + } + + private void save(TrafficFlowObserved trafficFlowObserved, Map<String, MongoCollection<Document>> collectionList, + List<String> trafficCols, MongoDatabase database, City city, JSONArray arrInserted, JSONArray arrUpdated, JSONArray arrNotInserted) + { + if (trafficFlowObserved.isValid()) + { + DatabaseAction documentAction = saveDocument(trafficFlowObserved, collectionList, trafficCols, database,city); + DatabaseAction metricAction = saveMetric(trafficFlowObserved, city); + + if (documentAction.actionType == DatabaseActionTypeEnum.INSERTED && metricAction.actionType == DatabaseActionTypeEnum.INSERTED) + { + registry(trafficFlowObserved, city, arrInserted); + } + else if (documentAction.actionType == DatabaseActionTypeEnum.UPDATED || metricAction.actionType == DatabaseActionTypeEnum.UPDATED ) + { + registry(trafficFlowObserved, city, arrUpdated); + } + else if (documentAction.actionType == DatabaseActionTypeEnum.NOT_INSERTED ) + { + registry(trafficFlowObserved, city, arrNotInserted, documentAction.reason ); + } + else if (metricAction.actionType == DatabaseActionTypeEnum.NOT_INSERTED ) + { + registry(trafficFlowObserved, city, arrNotInserted, metricAction.reason ); + } + } + else + { + registry(trafficFlowObserved, city, arrNotInserted, "Wrong input data, missing some required field(s) or wrong values."); + } + } + + public APIResponse getTDataRangeOtherMethod(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Antes del For"); + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + + List<Document> results = new ArrayList<>(); + cursor.into(results); + + allResults.addAll(results); + + int resSize = results.size(); + + //reached limit? + int total = allResults.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Despues del For"); + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Despues de procesamiento"); + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + ex.getMessage()); + } + + return res; + } + + public APIResponse getTDataRangeOtherMethod2(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + List<String> trafficCols = new ArrayList<String>(); + +// MongoDBManager mongoDB = new MongoDBManager(dbParams); +// if (mongoDB.connect()) { + + MongoDatabase database = mongoDBGlobal.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } +// mongoDB.close(); +// } +// else { +// res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); +// res.setError("Cann't connect to database."); +// } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Antes del For"); + + for (String collectionName: trafficCols) { + +// if (mongoDB.connect()) { + +// MongoDatabase database = mongoDBGlobal.getDatabase(); + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + + List<Document> results = new ArrayList<>(); + cursor.into(results); + + allResults.addAll(results); + + int resSize = results.size(); + + //reached limit? + int total = allResults.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + +// mongoDB.close(); + +// } +// else { +// res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); +// res.setError("Cann't connect to database."); +// } + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Despues del For"); + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Despues de procesamiento"); + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + public APIResponse getTDataRangeThreadsMethod(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + + + List<Thread> threads = new ArrayList<>(); + List<WorkerThread> workers = new ArrayList<>(); + + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + WorkerThread w = new WorkerThread(collectionName, cursor); + workers.add(w); + Thread t = new Thread(w); + threads.add(t); + t.start(); + } + + for(int i = 0; i < threads.size(); i++) { + try { + threads.get(i).join(); + } catch (InterruptedException ex) { + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Exception " + ex.getMessage()); + } + } + + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Antes de obtener resultados Threads"); + + for(int i = 0; i < threads.size(); i++) { + List<Document> results = workers.get(i).getResults(); + int numElems = results.size(); + if (numElems <= currentLimit) { + allResults.addAll(results); + currentLimit = currentLimit - numElems; + } else { + allResults.addAll(results.subList(0, currentLimit)); + break; + } + } + + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Despues de obtener resultados Threads"); + + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Despues de procesamiento"); + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + + logger.debug("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + logger.debug("TrafficFlowObserved::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + + APIResponse response = null; + + try + { + TrafficFlowObserved trafficFlowObserved = TrafficFlowObserved.createTrafficFlowObserved(data); + + if (trafficFlowObserved == null) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Input data is not in required format ('Traffic Flow Observation' object)"); + } + if (!trafficFlowObserved.isValid()) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Wrong input data, some required field(s) missing."); + } + if (trafficFlowObserved.getId().compareTo(id) != 0) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Wrong input data, IDs are different."); + } + + List<JSONObject> documentResponse = this.updateDocument(trafficFlowObserved, city, id ); + HttpResponse metricResponse = this.updateMetric(trafficFlowObserved, city); + + if (metricResponse.getStatusLine().getStatusCode() == HttpStatus.OK.value()) + { + // Llegados hasta aquĂ es que ha ido bien tanto mongoDb como openTsdb por lo que devolvemos la respuesta mongodb para no tener que unificar una respuesta. + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.OK + "]"); + return new APIResponse(HttpStatus.OK, documentResponse); + } + else + { + // Si ha ido mal opentsdb, entonces devolvemos su error nativo. + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()) + "]"); + return new APIResponse(HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()), metricResponse.getStatusLine().getReasonPhrase()); + } + } + + catch (MongoDbConnectionException ex) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.INTERNAL_SERVER_ERROR + "]"); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Cann't connect to database."); + } + catch (DocumentNotFoundException ex) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.NOT_FOUND + "]"); + return new APIResponse(HttpStatus.NOT_FOUND, ex.getMessage()); + } + catch (Exception ex) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.INTERNAL_SERVER_ERROR + "]"); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR,"Server Error: " + ex.getMessage()); + } + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "TrafficFlowObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "TrafficFlowObserved", TrafficFlowObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"address\": {\r\n" + + " \"addressCountry\": \"ES\",\r\n" + + " \"addressLocality\": \"Valladolid\",\r\n" + + " \"streetAddress\": \"Avenida de Salamanca\",\r\n" + + " \"type\": \"PostalAddress\"\r\n" + + " },\r\n" + + " \"averageHeadwayTime\": 0.5,\r\n" + + " \"averageVehicleLength\": 9.87,\r\n" + + " \"averageVehicleSpeed\": 52.6,\r\n" + + " \"dateObserved\": \"2016-12-07T11:10:00Z\",\r\n" + + " \"id\": \"urn:ngsi-ld:TrafficFlowObserved:TrafficFlowObserved-Valladolid-osm-60821110\",\r\n" + + " \"intensity\": 197,\r\n" + + " \"laneDirection\": \"forward\",\r\n" + + " \"laneId\": 1,\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " [\r\n" + + " -4.73735395519672,\r\n" + + " 41.6538181849672\r\n" + + " ],\r\n" + + " [\r\n" + + " -4.73414858659993,\r\n" + + " 41.6600594193478\r\n" + + " ],\r\n" + + " [\r\n" + + " -4.73447575302641,\r\n" + + " 41.659585195093\r\n" + + " ]\r\n" + + " ],\r\n" + + " \"type\": \"LineString\"\r\n" + + " },\r\n" + + " \"occupancy\": 0.76,\r\n" + + " \"reversedLane\": false,\r\n" + + " \"type\": \"TrafficFlowObserved\"\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "TrafficFlowObserved",TrafficFlowObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "TrafficFlowObserved",TrafficFlowObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::IN: Request for data with id = " + id); + + try + { + if (id.isEmpty()) + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST, "Empty ID."); + } + + Document document = this.deleteDocument(id, city); + HttpResponse metricResponse = this.deleteMetric(city, document.getString("name"), document.getDate("dateObserved").getTime()); + + if (metricResponse.getStatusLine().getStatusCode() == HttpStatus.OK.value()) + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::OUT [" + HttpStatus.OK + "]"); + return new APIResponse(HttpStatus.OK, this.buildResponse(id)); + } + else + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::OUT [" + HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()) + "]"); + return new APIResponse(HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()), metricResponse.getStatusLine().getReasonPhrase()); + } + } + catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR,"Server Error: " + ex.getMessage()); + } + } + + private Document deleteDocument(String id, City city) throws MongoDbConnectionException, DocumentNotFoundException, OpenTsdbOperationException + { + TrafficFlowObserved trafficFlowObserved = null; + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + + if (!mongoDB.connect()) + { + throw new MongoDbConnectionException(); + } + + MongoDatabase database = mongoDB.getDatabase(); + boolean found = false; + + List<String> collectionsName = getCollectionsByPrefix(database, city); + + Document document = null; + for (String collectionName: collectionsName) + { + MongoCollection<Document> collection = database.getCollection(collectionName); + document = collection.find(new BasicDBObject("_id", id)).first(); + + if (document != null) + { + found = true; + + DeleteResult result = collection.deleteOne(new Document("_id", id)); + + if (result.getDeletedCount() == 0) + { + throw new OpenTsdbOperationException("Could not be deleted the id requested."); + } + + break; + } + } + if (!found) + { + throw new DocumentNotFoundException("Document '" + id + "' not found."); + } + mongoDB.close(); + + return document; + } + + private HttpResponse deleteMetric( City city, String idSpiral, long timestamp) throws Exception + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + return opentsdbClient.Delete("trafficflowobserved.intensity", city.name(), idSpiral, timestamp); + } + + + private DatabaseAction saveDocument(TrafficFlowObserved record,Map<String, MongoCollection<Document>> collectionList, + List<String> trafficCols, MongoDatabase database, City city) + { + DatabaseAction databaseAction = null; + Document doc = null; + MongoCollection<Document> coll = null; + + try + { + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + LocalDateTime ldateObs =LocalDateTime.ofInstant(dateObs.toInstant(), ZoneId.of("UTC")); + int year = ldateObs.getYear(); + int month = ldateObs.getMonthValue(); + String yearMonth = year + "_" + (month<10?"0":"") + month; + + if (collectionList.containsKey(yearMonth)) + { + coll = collectionList.get(yearMonth); + } + else + { + String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_" + yearMonth).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(yearMonth, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (!trafficCols.contains(collectionName)) + { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + doc.append("dateObserved", dateObs); + + Date dateObsTo = null; + + if (record.getDateObservedTo() != null) + { + if (!record.getDateObservedTo().isEmpty() && record.getDateObservedTo().compareToIgnoreCase("null") != 0) + dateObsTo= Utils.ISO2Date(record.getDateObservedTo()); + } + if (dateObsTo != null) doc.append("dateObservedTo", dateObsTo); + + Date dateObsFrom = null; + if (record.getDateObservedFrom() != null) + { + if (!record.getDateObservedFrom().isEmpty() && record.getDateObservedFrom().compareToIgnoreCase("null") != 0) + dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + } + if (dateObsFrom != null) doc.append("dateObservedFrom", dateObsFrom); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.INSERTED, record.getId()); + } + catch (MongoWriteException e) + { + int errorCode = e.getCode(); + if (errorCode == 11000) + { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + + if (existing != null) + { + try + { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + //fields dateObservedFrom and dateObservedTo: if not set, use observation date + Date dateObsFrom = null; + if (record.getDateObservedFrom() != null) { + if (record.getDateObservedFrom().isEmpty() == false && record.getDateObservedFrom().compareToIgnoreCase("null") != 0) + dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + } + if (dateObsFrom != null) doc.append("dateObservedFrom", dateObsFrom); + Date dateObsTo = null; + if (record.getDateObservedTo() != null) { + if (record.getDateObservedTo().isEmpty() == false && record.getDateObservedTo().compareToIgnoreCase("null") != 0) + dateObsTo= Utils.ISO2Date(record.getDateObservedTo()); + } + if (dateObsTo != null) doc.append("dateObservedTo", dateObsTo); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.UPDATED); + + } + catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, ex.getMessage()); + } + } + } + } + catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, ex.getMessage()); + } + return databaseAction; + + } + + private List<JSONObject> updateDocument(TrafficFlowObserved trafficFlowObserved, City city, String id) throws JSONException, MongoDbConnectionException, DocumentNotFoundException { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + + if (!mongoDB.connect()) + { + throw new MongoDbConnectionException(); + } + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(trafficFlowObserved.getDateObserved()); + + MongoCollection<Document> collection = this.getCollection(database, city, dateObs); + + Document documentFromDatabase = collection.find(Filters.eq("_id", id)).first(); + + if ( documentFromDatabase == null) + { + throw new DocumentNotFoundException("Document '" + id + "' not found."); + } + + Document document = Document.parse(Utils.Object2JSON(trafficFlowObserved)); + document.remove("id"); + //need to replace "context" for "@context" + document.put("@context", trafficFlowObserved.getContext()); + document.remove("context"); + //creation date is the same as original + document.append("dateCreated", documentFromDatabase.get("dateCreated")); + //modification date is now + document.append("dateModified", new Date()); + document.append("dateObserved", dateObs); + //fields dateObservedFrom and dateObservedTo: if not set, use observation date + Date dateObsFrom = null; + if (trafficFlowObserved.getDateObservedFrom() != null) + { + if (trafficFlowObserved.getDateObservedFrom().isEmpty() == false && trafficFlowObserved.getDateObservedFrom().compareToIgnoreCase("null") != 0) + dateObsFrom = Utils.ISO2Date(trafficFlowObserved.getDateObservedFrom()); + } + if (dateObsFrom != null) document.append("dateObservedFrom", dateObsFrom); + Date dateObsTo = null; + if (trafficFlowObserved.getDateObservedTo() != null) + { + if (trafficFlowObserved.getDateObservedTo().isEmpty() == false && trafficFlowObserved.getDateObservedTo().compareToIgnoreCase("null") != 0) + dateObsTo= Utils.ISO2Date(trafficFlowObserved.getDateObservedTo()); + } + if (dateObsTo != null) document.append("dateObservedTo", dateObsTo); + + collection.replaceOne(Filters.eq("_id", id), document); + + mongoDB.close(); + + return this.buildResponse(document,id); + } + + private MongoCollection<Document> getCollection(MongoDatabase database, City city, Date dateObserved) + { + int year = dateObserved.getYear() + 1900; + int month = dateObserved.getMonth() + 1; + String yearMonth = year + "_" + (month<10?"0":"") + month; + String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_" + yearMonth).toLowerCase(); + return database.getCollection(collectionName); + } + + private List<String> getCollectionsByPrefix(MongoDatabase database, City city) + { + List<String> trafficCollections = new ArrayList<String>(); + //get names of collections, filter by model, and order in "descending" order (recent first) + String collectionNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> collectionsNames = database.listCollectionNames(); + for (String collectionName: collectionsNames) + { + if (collectionName.toLowerCase().startsWith(collectionNamePrefix)) + { + trafficCollections.add(collectionName.toLowerCase()); + } + } + Collections.sort(trafficCollections, Collections.reverseOrder()); //recent first + + return trafficCollections; + } + + private DatabaseAction saveMetric(TrafficFlowObserved trafficFlowObserved, City city) + { + DatabaseAction action = null; + + try + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + HttpResponse response = opentsdbClient.Post(new PointBuilder().addMetric("trafficflowobserved.intensity") + .addTimestamp( Objects.requireNonNull(Utils.ISO2Date(trafficFlowObserved.getDateObserved())).getTime() /1000) + .addValue(trafficFlowObserved.getIntensity()) + .addTag("city", city.name()) + .addTag("id_spiral",trafficFlowObserved.getName()).build()); + + if ( response == null) + { + action = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, "No response from the server"); + } + // + else if ( response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) + { + action = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, response.getStatusLine().getReasonPhrase()); + } + else if ( response.getStatusLine().getStatusCode() == HttpStatus.OK.value() && + JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject().get("success").toString().equals("1")) + { + action = new DatabaseAction(DatabaseActionTypeEnum.INSERTED ); + } + } + catch (IOException ex) + { + logger.error(ex.getMessage(), ex); + return new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, "It could not connect with the opentsdb server."); + } + + return action; + } + + private HttpResponse updateMetric(TrafficFlowObserved trafficFlowObserved, City city) throws Exception + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + return opentsdbClient.Post(new PointBuilder().addMetric("trafficflowobserved.intensity") + .addTimestamp( Objects.requireNonNull(Utils.ISO2Date(trafficFlowObserved.getDateObserved())).getTime() /1000) + .addValue(trafficFlowObserved.getIntensity()) + .addTag("city", city.name()) + .addTag("id_spiral",trafficFlowObserved.getName()).build()); + } + + private void registry(TrafficFlowObserved trafficFlowObserved, City city, JSONArray jsonArray) + { + registry(trafficFlowObserved, city, jsonArray,null); + } + + private void registry(TrafficFlowObserved trafficFlowObserved, City city, JSONArray jsonArray, String message) + { + JSONObject jsonObject = new JSONObject(); + try + { + jsonObject.put("id", trafficFlowObserved.getId()); + if(message != null) + { + jsonObject.put("reason", message); + } + jsonArray.put(jsonObject); + } + catch (JSONException e) + { + logger.error("TrafficFlowObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + private List<JSONObject> buildResponse( JSONArray arrInserted, JSONArray arrNotInserted, JSONArray arrUpdated, City city) + { + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + try + { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } + catch (JSONException e) + { + logger.error("TrafficFlowObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + return lstRes; + } + + private List<JSONObject> buildResponse( Document document, String id) throws JSONException { + List<JSONObject> jsonObjectList = new ArrayList<JSONObject>(); + JSONObject jsonObject = new JSONObject(); + + document.put("id", id); + jsonObject.put("updatedData", new JSONObject(document.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()))); + jsonObjectList.add(jsonObject); + return jsonObjectList; + } + + public List<JSONObject> buildResponse( String id) throws JSONException + { + List<JSONObject> jsonObjectList = new ArrayList<JSONObject>(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("deleted", id); + jsonObjectList.add(jsonObject); + return jsonObjectList; + } + + public class WorkerThread implements Runnable { + private List<Document> results = null; + FindIterable<Document> cursor = null; + String collectionName = ""; + + public WorkerThread(String collectionName, FindIterable<Document> cursor) { + results = new ArrayList<>(); + this.cursor = cursor; + this.collectionName = collectionName; + } + + @Override + public void run() { + if (cursor != null) { + long start = System.currentTimeMillis(); + cursor.into(results); + logger.info(collectionName + " --> " + (System.currentTimeMillis() - start)); + } + } + + public List<Document> getResults(){ + return results; + } + + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TransportStationController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TransportStationController.java new file mode 100644 index 0000000000000000000000000000000000000000..117d92f1e520077aa26400a6a2caa0e5c845b46f --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TransportStationController.java @@ -0,0 +1,679 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTrip; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTripDataModel; +import com.tecnalia.urbanite.storage.DataModel.TransportStation.TransportStation; +import com.tecnalia.urbanite.storage.DataModel.TransportStation.TransportStationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class TransportStationController extends GenericController implements IGenericController { + + private Logger logger = LoggerFactory.getLogger(TransportStationController.class); + private DBConfiguration.DBParams dbParams; + + + private MongoDBManager mongoDBGlobal = null; + + + + public TransportStationController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + this.mongoDBGlobal = new MongoDBManager(dbParams); + this.mongoDBGlobal.connect(); + } + + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("TransportStation::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + for (int i = 0; i < arrData.length(); i++) { + TransportStation record = TransportStation.createTransportStation(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("TransportStation::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + String collectionName = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + coll = database.getCollection(collectionName); + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported= Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TransportStation::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TransportStation::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("TransportStation::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TransportStation' objects)"); + } + + } catch (Exception e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TransportStation' objects)"+ e2.getMessage()); + } + + logger.debug("TransportStation::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TransportStation::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TransportStation.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TransportStation.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String collectionName = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + coll = database.getCollection(collectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateModified", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TransportStation::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + + querySort.append("dateLastReported", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + } catch (JSONException e) { + logger.error("TransportStation::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + //reached limit? + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.transportStation + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.transportStation + "' data model's fields."); + } + + logger.debug("TransportStation::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("TransportStation::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + try { + TransportStation record = TransportStation.createTransportStation(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //o.put("updatedData", doc); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse--> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('TransportStation' object)"); + } + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + logger.debug("TransportStation::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "TransportStation"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "TransportStation", TransportStation.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ \r\n" + + " \"id\": \"urn:ngsi-ld:Station:Station:MNCA-STram-L02-AP-T2\", \r\n" + + " \"type\": \"Station\", \r\n" + + " \"name\": \"NCE-Tram-Station-L02-AP-T2\", \r\n" + + " \"alternateName\": \"Nice - Tramway Station Description - L02-AP-T2\", \r\n" + + " \"description\": \"Description and services provided in the station\", \r\n" + + " \"seeAlso\": \"http://tramway.nice.fr/wp-content/uploads/2019/10/BD_pocket_plan_MAJ03_2019_20082019.pdf\", \r\n" + + " \"location\": { \r\n" + + " \"type\": \"Point\", \r\n" + + " \"coordinates\": [ \r\n" + + " 43.66481, \r\n" + + " 7.196545 \r\n" + + " ] \r\n" + + " }, \r\n" + + " \"address\": { \r\n" + + " \"addressCountry\": \"FR\", \r\n" + + " \"addressLocality\": \"Nice\", \r\n" + + " \"streetAddress\": \"Airport - Terminal 2 - Door A2\" \r\n" + + " }, \r\n" + + " \"areaServed\": \"Nice Airport\", \r\n" + + " \"dateLastReported\": \"2020-03-17T08:45:00Z\", \r\n" + + " \"dateObserved\": \"2020-03-17T08:45:00Z\", \r\n" + + " \"stationType\": [ \r\n" + + " \"tram\" \r\n" + + " ], \r\n" + + " \"locationType\": 1, \r\n" + + " \"levelId\": 0, \r\n" + + " \"zoneId\": \"B\", \r\n" + + " \"wheelChairAccessible\": 1, \r\n" + + " \"openingHoursSpecification\": [ \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Monday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Tuesday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Wednesday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Thursday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Friday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Saturday\", \r\n" + + " \"opens\": \"08:00:00\", \r\n" + + " \"closes\": \"23:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Sunday\", \r\n" + + " \"opens\": \"08:30:00\", \r\n" + + " \"closes\": \"21:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"PublicHolidays\", \r\n" + + " \"opens\": \"08:30:00\", \r\n" + + " \"closes\": \"21:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " } \r\n" + + " ], \r\n" + + " \"owner\": [ \r\n" + + " \"uri:ngsi:StreetRetail\" \r\n" + + " ], \r\n" + + " \"contractingAuthority\": \"MNCA - Metropole Nice Cote d'Azur\", \r\n" + + " \"contractingCompagny\": \"R\\u00e9gie Ligne d'Azur\", \r\n" + + " \"contactPoint\": { \r\n" + + " \"url\": \"uri:ngsi:www.lignesdazur.com\" \r\n" + + " }, \r\n" + + " \"webSite\": \"https://tramway.nice.fr/Plan-Station-L02-AP-T2.pdf\", \r\n" + + " \"instalationMode\": \"ground\", \r\n" + + " \"dimension\": { \r\n" + + " \"length\": 300, \r\n" + + " \"width\": 25, \r\n" + + " \"thickness\": 6.35 \r\n" + + " }, \r\n" + + " \"inventory\": { \r\n" + + " \"nbOfIOPoint\": 2, \r\n" + + " \"nbOfLane\": 1, \r\n" + + " \"nbOfPlatform\": 1, \r\n" + + " \"PlatformType\": [ \r\n" + + " \"lateral\" \r\n" + + " ] \r\n" + + " }, \r\n" + + " \"stationConnected\": [ \r\n" + + " { \r\n" + + " \"stationType\": \"tram\", \r\n" + + " \"linesConnected\": [ \r\n" + + " \"Tram 2 - CADAM / Nikaia\", \r\n" + + " \"Tram 3 - Saint Isidore / Stade Allianz Riviera\" \r\n" + + " ] \r\n" + + " }, \r\n" + + " { \r\n" + + " \"stationType\": \"train\", \r\n" + + " \"linesConnected\": [ \r\n" + + " \"Gare SNCF Nice Saint Augustin (600m)\" \r\n" + + " ] \r\n" + + " }, \r\n" + + " { \r\n" + + " \"stationType\": \"bus\", \r\n" + + " \"linesConnected\": [ \r\n" + + " \"L20 - Giono / Les Pugets\", \r\n" + + " \"L20 - Centre Commercial St Isidore\", \r\n" + + " \"L21 - Le Gu\\u00e9 / Polygone Riviera\", \r\n" + + " \"L54 - Centre Commercial Cap 3000 - St Jeannet\", \r\n" + + " \"L90 - La Bolline\", \r\n" + + " \"91 Auron\", \r\n" + + " \"L92 - Isola 2000\" \r\n" + + " ] \r\n" + + " } \r\n" + + " ], \r\n" + + " \"services\": { \r\n" + + " \"purchaseDevice\": true, \r\n" + + " \"interactiveDevice\": true, \r\n" + + " \"timetableDevice\": true, \r\n" + + " \"voiceDevice\": true, \r\n" + + " \"informationBoardDevice\": true, \r\n" + + " \"messageDevice\": false, \r\n" + + " \"shelters\": true, \r\n" + + " \"restBench\": false, \r\n" + + " \"emergencyPhone\": false, \r\n" + + " \"videoSurveillance\": true, \r\n" + + " \"defibrillator\": false, \r\n" + + " \"wheelChairAccessible\": true \r\n" + + " }, \r\n" + + " \"paymentAccepted\": [ \r\n" + + " \"Cash\", \r\n" + + " \"CreditCard\" \r\n" + + " ], \r\n" + + " \"currencyAccepted\": [ \r\n" + + " \"EUR\" \r\n" + + " ], \r\n" + + " \"constructionDate\": \"2016-19-08\", \r\n" + + " \"commissioningDate\": \"2018-09-15\", \r\n" + + " \"architect\": \"Nice Architecture\", \r\n" + + " \"featuredArtist \": [ \r\n" + + " \"Leopold\", \r\n" + + " \"De Renaiss\" \r\n" + + " ], \r\n" + + " \"@context\": [ \r\n" + + " \"https://smartdatamodels.org/context.jsonld\", \r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\" \r\n" + + " ] \r\n" + + "} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("TransportStation::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "TransportStation",TransportStation.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "TransportStation",TransportStation.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + + return this.deleteDataByID(city, id, collNamePrefix, "TransportStation"); + + } + +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/VehicleController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/VehicleController.java new file mode 100644 index 0000000000000000000000000000000000000000..37e1fa01a211fd95dcfb8c9de6fbdd3af6c443db --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/VehicleController.java @@ -0,0 +1,458 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.DataModel.Transportation.Vehicle; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class VehicleController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(VehicleController.class); + + public VehicleController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("Vehicle::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (TransportationDataModel.VEHICLEOBSERVED+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + Vehicle record = Vehicle.createVehicle(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Vehicle::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + Date observationDateTime = null; + + if (record.getObservationDateTime() != null) + { + if (!record.getObservationDateTime().isEmpty() && record.getObservationDateTime().compareToIgnoreCase("null") != 0) + observationDateTime= Utils.ISO2Date(record.getObservationDateTime()); + } + if (observationDateTime != null) doc.append("observationDateTime", observationDateTime); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + Date observationDateTime = null; + + if (record.getObservationDateTime() != null) + { + if (!record.getObservationDateTime().isEmpty() && record.getObservationDateTime().compareToIgnoreCase("null") != 0) + observationDateTime= Utils.ISO2Date(record.getObservationDateTime()); + } + if (observationDateTime != null) doc.append("observationDateTime", observationDateTime); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Vehicle::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Vehicle::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Vehicle::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + logger.debug("GtfsShape::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("Vehicle::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(Vehicle.class, filters)) { + //check the return fields + if (Utils.typeHasFields(Vehicle.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + MongoCollection<Document> coll = null; + String CollectionName = (TransportationDataModel.VEHICLEOBSERVED+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("observationDateTime", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("Vehicle::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateModified", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateModified") == false) returnFields.add("dateModified"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("Vehicle::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + TransportationDataModel.VEHICLEOBSERVED + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + TransportationDataModel.VEHICLEOBSERVED + "' data model's fields."); + } + logger.debug("Vehicle::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("Vehicle::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + Vehicle record = Vehicle.createVehicle(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('PointOfInterest' object)"); + } + + logger.debug("Vehicle::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "Vehicle"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "observationDateTime", collNamePrefix, "Vehicle", Vehicle.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("Vehicle::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "Vehicle",Vehicle.class); + + + } + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "Vehicle",Vehicle.class); + + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "Vehicle"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/WeatherObservedController.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/WeatherObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..eadfa7278de334eaa1394c6398136c412e8d13d7 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/WeatherObservedController.java @@ -0,0 +1,557 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.TransportStation.TransportStation; +import com.tecnalia.urbanite.storage.DataModel.Weather.WeatherDataModel; +import com.tecnalia.urbanite.storage.DataModel.Weather.WeatherObserved; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class WeatherObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(WeatherObservedController.class); + + public WeatherObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("WeatherObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED+ "_" + city + "_").toLowerCase(); + + List<String> weatherColls = new ArrayList<String>(); + + weatherColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + for (int i = 0; i < arrData.length(); i++) { + WeatherObserved record = WeatherObserved.createWeatherObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + LocalDateTime ldateObs =LocalDateTime.ofInstant(dateObs.toInstant(), ZoneId.of("UTC")); + int year = ldateObs.getYear(); + + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (weatherColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + doc.append("dateObserved", dateObs); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Weather Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Weather Observation' objects)"); + } + + logger.debug("WeatherObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("WeatherObserved::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(WeatherObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(WeatherObserved.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + int startYear = 0; + int endYear = 0; + if (startDate != null) startYear = startDate.getYear() + 1900; + if (endDate != null) endYear = endDate.getYear() + 1900; + + //get the different collections between dates + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> weatherColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + + try { + int year = Integer.parseInt(collectionName.substring(collNamePrefix.length())); + //no errors --> check if it's in range + boolean addToList = true; + if (startYear > 0 && year < startYear) addToList = false; + if (endYear > 0 && year > endYear) addToList = false; + if (addToList) + weatherColls.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR" format --> omit table + } + + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(weatherColls); + else + Collections.sort(weatherColls, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("WeatherObserved::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: weatherColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("WeatherObserved::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.weatherObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.weatherObserved + "' data model's fields."); + } + + logger.debug("WeatherObserved::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("WeatherObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + WeatherObserved record = WeatherObserved.createWeatherObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = dateObs.getYear() + 1900; + String collectionName = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Weather Observation' object)"); + } + + logger.debug("WeatherObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "WeatherObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "WeatherObserved", WeatherObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:WeatherObserved:Spain-WeatherObserved-Valladolid-2016-11-30T07:00:00.00Z\",\r\n" + + " \"type\": \"WeatherObserved\",\r\n" + + " \"dateObserved\": \"2016-11-30T07:00:00.00Z\",\r\n" + + " \"temperature\": 3.3,\r\n" + + " \"precipitation\": 0,\r\n" + + " \"atmosphericPressure\": 1024,\r\n" + + " \"pressureTendency\": 0.5,\r\n" + + " \"refDevice\": \"urn:ngsi-ld:Device:device-0A3478\",\r\n" + + " \"source\": \"http://www.aemet.es\",\r\n" + + " \"location\": {\r\n" + + " \"type\": \"Point\",\r\n" + + " \"coordinates\": [\r\n" + + " -4.754444444,\r\n" + + " 41.640833333\r\n" + + " ]\r\n" + + " },\r\n" + + " \"address\": {\r\n" + + " \"addressLocality\": \"Valladolid\",\r\n" + + " \"addressCountry\": \"ES\"\r\n" + + " },\r\n" + + " \"dataProvider\": \"TEF\",\r\n" + + " \"relativeHumidity\": 1,\r\n" + + " \"streamGauge\": 50,\r\n" + + " \"snowHeight\": 20,\r\n" + + " \"windDirection\": 120,\r\n" + + " \"windSpeed\": 10,\r\n" + + " \"uvIndexMax\": 1,\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\"\r\n" + + " ]\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("WeatherObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "WeatherObserved",WeatherObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "WeatherObserved",WeatherObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "WeatherObserved"); + + } + +} diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/DatabaseAction.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/DatabaseAction.java new file mode 100644 index 0000000000000000000000000000000000000000..9c103c767b9d66bb2833e41f592c0e56756ea794 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/DatabaseAction.java @@ -0,0 +1,36 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers.models; + +import com.tecnalia.urbanite.storage.controllers.models.enums.DatabaseActionTypeEnum; + +public class DatabaseAction +{ + public DatabaseActionTypeEnum actionType; + public String id; + public String reason; + + public DatabaseAction(DatabaseActionTypeEnum actionType) + { + this(actionType, null); + } + + public DatabaseAction(DatabaseActionTypeEnum actionType, String reason) + { + this.actionType = actionType; + this.reason = reason; + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/enums/DatabaseActionTypeEnum.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/enums/DatabaseActionTypeEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..a9edf2b06c93d18dfe3ee8a382dc98491c95de27 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/enums/DatabaseActionTypeEnum.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers.models.enums; + +public enum DatabaseActionTypeEnum +{ + INSERTED, + UPDATED, + NOT_INSERTED +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/DocumentNotFoundException.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/DocumentNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..a6ccb584db31a0949d83e169e5e68e211e14e630 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/DocumentNotFoundException.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.exception; + +public class DocumentNotFoundException extends Exception +{ + public DocumentNotFoundException(String errorMessage) + { + super(errorMessage); + } + public DocumentNotFoundException() + { + super(); + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/MongoDbConnectionException.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/MongoDbConnectionException.java new file mode 100644 index 0000000000000000000000000000000000000000..e4e088cb5c80f9bad4157ab4a8390ad7c15f11d5 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/MongoDbConnectionException.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.exception; + +public class MongoDbConnectionException extends Exception +{ + public MongoDbConnectionException(String errorMessage) + { + super(errorMessage); + } + public MongoDbConnectionException() + { + super(); + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/OpenTsdbOperationException.java b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/OpenTsdbOperationException.java new file mode 100644 index 0000000000000000000000000000000000000000..17ca09a0598109c73cc1c36f703909e70859d5f9 --- /dev/null +++ b/dataStorage/shared/src/main/java/com/tecnalia/urbanite/storage/exception/OpenTsdbOperationException.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.exception; + +public class OpenTsdbOperationException extends Exception +{ + public OpenTsdbOperationException(String errorMessage) + { + super(errorMessage); + } + public OpenTsdbOperationException() + { + super(); + } +} \ No newline at end of file diff --git a/dataStorage/shared/src/main/resources/application.properties b/dataStorage/shared/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..3cb66a8cb1326910d12917b5f5dc592c29c4c815 --- /dev/null +++ b/dataStorage/shared/src/main/resources/application.properties @@ -0,0 +1,5 @@ +application-description=@project.description@ +application-version=@project.version@ +logging.level.org.springframework.boot.autoconfigure=ERROR +logging.level.com.tecnalia.urbanite.storage=DEBUG + diff --git a/dataStorage/shared/src/main/resources/static/datamodels.html b/dataStorage/shared/src/main/resources/static/datamodels.html new file mode 100644 index 0000000000000000000000000000000000000000..27f521d06575e03301dbc4c8386585e9fe46c53c --- /dev/null +++ b/dataStorage/shared/src/main/resources/static/datamodels.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="ISO-8859-1"> +<title>URBANITE - Data Models</title> +</head> +<body> +<p> Data Model </p> +</body> +</html> \ No newline at end of file diff --git a/dataStorage/shared/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java b/dataStorage/shared/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eff4c40d3683ca586ccd3e136a19e2ee320265b6 --- /dev/null +++ b/dataStorage/shared/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java @@ -0,0 +1,456 @@ +// +//package com.tecnalia.urbanite.storage.functional; +// +//import com.google.gson.*; +//import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +//import com.tecnalia.urbanite.storage.Utils.Utils; +//import org.apache.http.HttpResponse; +//import org.apache.http.client.methods.HttpDelete; +//import org.apache.http.client.methods.HttpGet; +//import org.apache.http.client.methods.HttpPost; +//import org.apache.http.client.methods.HttpPut; +//import org.apache.http.conn.ssl.NoopHostnameVerifier; +//import org.apache.http.conn.ssl.TrustAllStrategy; +//import org.apache.http.entity.StringEntity; +//import org.apache.http.impl.client.HttpClientBuilder; +//import org.apache.http.ssl.SSLContextBuilder; +//import org.apache.http.util.EntityUtils; +//import org.junit.jupiter.api.Assertions; +//import org.junit.jupiter.api.Test; +//import org.springframework.http.HttpStatus; +// +//import java.io.BufferedReader; +//import java.io.IOException; +//import java.io.InputStream; +//import java.io.InputStreamReader; +//import java.net.URLEncoder; +//import java.nio.charset.StandardCharsets; +//import java.security.KeyManagementException; +//import java.security.KeyStoreException; +//import java.security.NoSuchAlgorithmException; +//import java.text.DateFormat; +//import java.text.SimpleDateFormat; +//import java.time.Duration; +//import java.util.Date; +//import java.util.TimeZone; +//import java.util.concurrent.ThreadLocalRandom; +// +//public class StorageFunctionalTest +//{ +// private final static String INSERT_URI = getApiUrl() + "/data/insertTData/trafficFlowObserved/bilbao"; +// private final static String UPDATE_URI = getApiUrl() + "/data/updateTData/trafficFlowObserved/bilbao"; +// private final static String DELETE_URI = getApiUrl() + "/data/deleteTData/trafficFlowObserved/bilbao"; +// private final static String AGGREGATE_URI = getApiUrl()+ "/data/aggregate/trafficFlowObserved/bilbao"; +// +// private static final Gson gson = new GsonBuilder() +// .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) +// .create(); +// +// public static String getApiUrl() +// { +// return System.getenv("API_TEST_URL") != null ? System.getenv("API_TEST_URL") : "http://localhost"; +// } +// +// @Test +// public void insert_empty() +// { +// try +// { +// HttpPost post = new HttpPost( INSERT_URI); +// post.addHeader("content-type", "application/json"); +// post.setEntity(new StringEntity("{}")); +// HttpResponse response = HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( post ); +// +// Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode()); +// +// String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); +// Assertions.assertEquals("Input data is not in required format (list of 'Traffic Flow Observation' objects)", +// JsonParser.parseString(responseString).getAsJsonObject().get("Error").getAsString()); +// +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// @Test +// public void insert_as_not_array() +// { +// try +// { +// String template = readFromInputStream("/traffic-flow-observed-template.json"); +// HttpResponse response = this.buildPost(template); +// Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode()); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// +// //Inserto un elemento +// @Test +// public void insert_one_element_as_array() +// { +// try +// { +// // inserto +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //Inserto dos elementos +// @Test +// public void insert_two_elements_as_array() +// { +// try +// { +// String trafficElement1 = this.getTrafficElement(); +// String trafficElement2 = this.getTrafficElement(); +// +// String trafficElementAsArray = "["+ trafficElement1 + "," + trafficElement2+ "]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement1).getAsJsonObject().get("id").toString() + "}," +// + "{\"id\":" + JsonParser.parseString(trafficElement2).getAsJsonObject().get("id").toString()+ "}]", jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement1); +// this.assertIntensity(trafficElement2); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //Inserto un elemento y lo vuelvo a insertar +// @Test +// public void insert_one_element_as_array_again() +// { +// try +// { +// // inserto +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // inserto de nuevo +// trafficElement = this.modifyIntensity(trafficElement); +// trafficElementAsArray = "["+trafficElement+"]"; +// response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //update un elemento +// @Test +// public void update_one_element() +// { +// try +// { +// // primero lo inserto para poder hacer luego el update +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // hago el update +// trafficElement = this.modifyIntensity(trafficElement); +// response = this.buildPut(trafficElement); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals(JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").getAsInt(), +// jsonObject.get("updatedData").getAsJsonObject().get("intensity").getAsInt()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //delete un elemento +// @Test +// public void delete_one_element() +// { +// try +// { +// // primero lo inserto para poder hacer luego el delete +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // hago el delete +// response = this.buildDelete(trafficElement); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// Assertions.assertEquals(JsonParser.parseString(trafficElement).getAsJsonObject().get("id").getAsString(), +// jsonObject.get("deleted").getAsString()); +// +// Thread.sleep(3000); +// +// // Lo vuelvo a consultar y no estĂ¡ +// Date start = Date.from(new Date().toInstant().minus(Duration.ofMinutes(5))); +// String id_spiral = JsonParser.parseString(trafficElement).getAsJsonObject().get("name").toString(); +// response = this.buildQuery(start, new Date(), AggregatorEnum.SUM, "{\"id_spiral\":\"" + id_spiral +"\"}"); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// Assertions.assertEquals("[]",EntityUtils.toString(response.getEntity())); // empty +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// +// //Comprueba que funciona la agregaciĂ³n. +// @Test +// public void aggregate_elements_sum() +// { +// try +// { +// int intensitySum = 0; +// String startDate = ""; +// String endDate = ""; +// +// StringBuilder trafficElementAsArray = new StringBuilder(); +// trafficElementAsArray.append("["); +// int elements = ThreadLocalRandom.current().nextInt(0, 20); +// for ( int i = 1; i<= elements ; i++) +// { +// String trafficElement = this.getTrafficElement(String.valueOf(i)); // Pass the intensity to evict duplicated spirals. +// intensitySum += JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").getAsInt(); +// if ( i==1) +// { +// startDate = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").getAsString(); +// } +// else if ( i == elements) +// { +// endDate = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").getAsString(); +// } +// trafficElementAsArray.append(trafficElement); +// if ( i != elements ) // We add the colon only when no is the last iteration. +// { +// trafficElementAsArray.append(","); +// } +// Thread.sleep(100); +// } +// trafficElementAsArray.append("]"); +// +// HttpResponse response = this.buildPost(trafficElementAsArray.toString()); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Thread.sleep(3000); +// +// this.assertSumIntensity(Utils.ISO2Date(startDate), Utils.ISO2Date(endDate), intensitySum); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// private String getTrafficElement() throws IOException +// { +// return this.getTrafficElement(String.valueOf(ThreadLocalRandom.current().nextInt(0, 300))); +// } +// +// private String getTrafficElement(String idSpiral) throws IOException +// { +// String template = readFromInputStream("/traffic-flow-observed-template.json"); +// +// Date dateObserved = new Date(); +// SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm"); +// formatter.setTimeZone(TimeZone.getTimeZone("UTC")); +// String strDateObserved = formatter.format(dateObserved); +// String dateObservedFormatted = strDateObserved.replace("/","").replace(":","").replace(" ",""); +// +// DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); +// df.setTimeZone(TimeZone.getTimeZone("UTC")); +// template = template.replace("#dateObserved#",df.format(dateObserved)); +// template = template.replace("#dateObservedFormat#", dateObservedFormatted); +// template = template.replace("#intensity#", String.valueOf(ThreadLocalRandom.current().nextInt(0, 300))); +// template = template.replace("#id_spiral#",idSpiral); +// return template; +// } +// +// private HttpResponse buildPost(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// HttpPost post = new HttpPost( INSERT_URI); +// post.addHeader("content-type", "application/json"); +// post.setEntity(new StringEntity(data)); +// +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( post ); +// } +// +// private HttpResponse buildPut(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// String id = JsonParser.parseString(data).getAsJsonObject().get("id").getAsString(); +// HttpPut put = new HttpPut( UPDATE_URI + "/" + URLEncoder.encode(id, StandardCharsets.UTF_8.toString())); +// put.addHeader("content-type", "application/json"); +// put.setEntity(new StringEntity(data)); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( put ); +// } +// +// private HttpResponse buildDelete(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// String id = JsonParser.parseString(data).getAsJsonObject().get("id").getAsString(); +// HttpDelete delete = new HttpDelete( DELETE_URI + "/" + URLEncoder.encode(id, StandardCharsets.UTF_8.toString())); +// delete.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( delete ); +// } +// +// private HttpResponse buildQuery(Date start, Date end, AggregatorEnum aggregatorEnum, String tags) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Conversion +// SimpleDateFormat simpleDateFormat = new SimpleDateFormat( +// "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); +// simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); +// +// String startDate = simpleDateFormat.format(start); +// String endDate = simpleDateFormat.format(end); +// +// String query = String.format(AGGREGATE_URI + "/intensity?startDate=%s&endDate=%s&aggregator=%s&tags=%s",startDate, endDate, aggregatorEnum, URLEncoder.encode(tags, StandardCharsets.UTF_8.toString())); +// HttpGet get = new HttpGet(query); +// get.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( get ); +// } +// +// private HttpResponse buildQuery(Date start, Date end,String downsample, AggregatorEnum aggregatorEnum ) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Conversion +// SimpleDateFormat simpleDateFormat = new SimpleDateFormat( +// "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); +// simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); +// +// String startDate = simpleDateFormat.format(start); +// String endDate = simpleDateFormat.format(end); +// +// String query = String.format(AGGREGATE_URI + "/intensity?startDate=%s&endDate=%s&downsample=%s&aggregator=%s",startDate, endDate,downsample, aggregatorEnum); +// HttpGet get = new HttpGet(query); +// get.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( get ); +// } +// +// private String readFromInputStream(String path) +// throws IOException { +// InputStream inputStream = this.getClass().getResourceAsStream(path); +// StringBuilder resultStringBuilder = new StringBuilder(); +// try (BufferedReader br +// = new BufferedReader(new InputStreamReader(inputStream))) { +// String line; +// while ((line = br.readLine()) != null) { +// resultStringBuilder.append(line).append("\n"); +// } +// } +// return resultStringBuilder.toString(); +// } +// +// private void assertIntensity(String trafficElement) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Lo consulto +// Date start = Date.from(new Date().toInstant().minus(Duration.ofMinutes(5))); +// String id_spiral = JsonParser.parseString(trafficElement).getAsJsonObject().get("name").toString(); +// HttpResponse aggregatorResponse = this.buildQuery(start, new Date(), AggregatorEnum.SUM, "{\"id_spiral\":\"" + id_spiral +"\"}"); +// String result = EntityUtils.toString(aggregatorResponse.getEntity()); +// +// int intensityExpected = Integer.parseInt(JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").toString()); +// String dateObservedRecovery = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").toString().replace("\"",""); +// String dateObservedEpoch = String.valueOf(Utils.ISO2Date(dateObservedRecovery).getTime()/1000); +// int intensityActual = JsonParser.parseString(result).getAsJsonArray().get(0).getAsJsonObject().get("dps").getAsJsonObject().get(dateObservedEpoch).getAsInt(); +// +// Assertions.assertEquals(intensityExpected, intensityActual); +// } +// +// private void assertSumIntensity(Date start, Date end, int sumIntensityExpected) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// end = Date.from(end.toInstant().plus(Duration.ofMinutes(1))); +// HttpResponse aggregatorResponse = this.buildQuery(start, end, "all-sum", AggregatorEnum.SUM); +// String result = EntityUtils.toString(aggregatorResponse.getEntity()); +// +// String startEpoch = String.valueOf(start.getTime()/1000); +// int sumIntensityActual = JsonParser.parseString(result).getAsJsonArray().get(0).getAsJsonObject().get("dps").getAsJsonObject().get(startEpoch).getAsInt(); +// Assertions.assertEquals(sumIntensityExpected, sumIntensityActual); +// } +// +// private String modifyIntensity(String trafficElement) +// { +// JsonObject jsonObject1 = JsonParser.parseString(trafficElement).getAsJsonObject(); +// jsonObject1.remove("intensity"); +// int newIntensity = ThreadLocalRandom.current().nextInt(0, 300); +// jsonObject1.addProperty("intensity", newIntensity); +// trafficElement = jsonObject1.toString(); +// return trafficElement; +// } +//} +// diff --git a/dataStorage/shared/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java b/dataStorage/shared/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java new file mode 100644 index 0000000000000000000000000000000000000000..09f6ff2dbffa077b73c6e2d2c6cb78b0d375d12a --- /dev/null +++ b/dataStorage/shared/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java @@ -0,0 +1,133 @@ +package com.tecnalia.urbanite.storage.tools; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.tecnalia.urbanite.storage.Utils.Utils; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +/** + * export the trafficflowobserved from the database mongo db to database opentsdb. + */ +public class Export { + + private static final String GET_URL = "https://bilbao.urbanite.esilab.org/data/getTDataRange/trafficFlowObserved/bilbao"; + + public static void main(String[] args) throws IOException + { + LocalDateTime start = LocalDateTime.parse("2021-01-01T00:00:00.000Z",DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + LocalDateTime end = LocalDateTime.parse("2021-12-31T00:00:00.000Z",DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + File file = createFile("C:\\FicherosUrbanite\\traffic-2021.txt"); + BufferedWriter writer = new BufferedWriter(new FileWriter(file, true)); + + File errorsFile = createFile("C:\\FicherosUrbanite\\errors.txt"); + BufferedWriter errorWriter = new BufferedWriter(new FileWriter(errorsFile, true)); + for (LocalDateTime iterationDate = start; iterationDate.isBefore(end); iterationDate = iterationDate.plusDays(1)) + { + System.out.println(iterationDate); + Map<String,String> map = new HashMap<>(); + + try + { + + String queryStart = iterationDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + LocalDateTime queryEndDatetime = iterationDate.plusHours(23).plusMinutes(59).plusSeconds(59).plusNanos(999); + String queryEnd = queryEndDatetime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + String nameForFile = queryStart.replace("-","").replace("-","").replace(":","").replace(".",""); + + + + HttpGet get = new HttpGet(String.format(GET_URL + "?startDate=%s&endDate=%s&limit=100000&sort=ASC", URLEncoder.encode(queryStart, StandardCharsets.UTF_8.toString()), URLEncoder.encode(queryEnd, StandardCharsets.UTF_8.toString()))); + get.addHeader("content-type", "application/json"); + HttpResponse response = HttpClientBuilder.create().build().execute( get ); + if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) + { + String json = EntityUtils.toString(response.getEntity()); + + if ( json != null && !json.isEmpty()) + { + JsonArray array = JsonParser.parseString(json).getAsJsonArray(); + + if (array != null && array.size() > 0) + { + if ( file != null) + { + + for (JsonElement element : array) + { + String dateObserved = element.getAsJsonObject().get("dateObserved").getAsString(); + String idSpiral = element.getAsJsonObject().get("name").getAsString(); + String intensity = element.getAsJsonObject().get("intensity").getAsString(); + + String key = dateObserved+idSpiral; + if ( !map.containsKey(key)) + { + writer.write(String.format("trafficflowobserved.intensity %s %s city=bilbao id_spiral=%s\n",Utils.ISO2Date(dateObserved).getTime()/1000, intensity, idSpiral)); + map.put(key, intensity); + } + } + + }else + { + errorWriter.write("Date:" + start + ". It could not create the file."); + } + } + else + { + errorWriter.write("Date:" + start + ". The array from the get response is null or empty."); + } + }else + { + errorWriter.write("Date:" + start + ". The entity is null or empty."); + } + } + else + { + errorWriter.write("Date:" + start + ". The response is null or empty."); + } + } + catch (Exception ex) + { + try { + errorWriter.write("Date:" + start + ". It ocurred an exception. " + ex.getMessage()); + + } + catch (Exception e) + { + System.out.println("Error!!"); + } + + System.out.println("Error!!"); + } + } + errorWriter.close(); + writer.close(); + } + + + private static File createFile(String name) throws IOException + { + File file = new File(name); + if ( file.exists()) + { + file.delete(); + } + + return file.createNewFile() != false ? file : null; + } +} diff --git a/dataStorage/shared/src/test/resources/traffic-flow-observed-template.json b/dataStorage/shared/src/test/resources/traffic-flow-observed-template.json new file mode 100644 index 0000000000000000000000000000000000000000..b831cc245ae5a9b1d71b0d5d7333200aef0f1d8b --- /dev/null +++ b/dataStorage/shared/src/test/resources/traffic-flow-observed-template.json @@ -0,0 +1,18 @@ +{ + "@context" : [ "https://smartdatamodels.org/context.jsonld", "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ], + "address" : { + "addressCountry" : "ES", + "addressLocality" : "Bilbao" + }, + "type" : "TrafficFlowObserved", + "location" : { + "coordinates" : [ [ [ 504417.9371092392, 4790030.564573327 ], [ 504453.27293873084, 4790030.564573327 ], [ 504453.27293873084, 4790133.21077546 ], [ 504417.9371092392, 4790133.21077546 ] ] ], + "type" : "Polygon" + }, + "dateObserved" : "#dateObserved#", + "occupancy" : 1, + "intensity" : #intensity#, + "averageVehicleSpeed" : 54, + "name" : #id_spiral#, + "id" : "urn:ngsi-ld:TrafficFlowObserved:#id_spiral#:#dateObservedFormat#" +} \ No newline at end of file diff --git a/dataStorage/src/main/java/com/tecnalia/urbanite/storage/AggregationAPI.java b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/AggregationAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..365878524de099f0b9736528261182cb19f4d81c --- /dev/null +++ b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/AggregationAPI.java @@ -0,0 +1,96 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import com.google.gson.Gson; +import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.Utils.Utils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotBlank; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@RestController +@Tag(name = "Data Aggregation", description = "Operations to aggregate Data") + +public class AggregationAPI { + + private final Logger logger = LoggerFactory.getLogger(AggregationAPI.class); + private static final Gson gson = new Gson(); + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Aggregate data from the database within a specific time range", description = "Returns aggregated values of type {model} from the database of the city {city}, according to the specified time range.") + @RequestMapping(path = "/aggregate/{model}/{city}/{metric}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> aggregate( + @PathVariable @NotBlank DataModel model, + @PathVariable @NotBlank City city, + @PathVariable @NotBlank @Parameter(description = "Available values: intensity", example = "intensity") String metric, + @Parameter(description= "", example="2021-02-15T00:00:00.000Z") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Type of the aggregation") @RequestParam(name="aggregator",required = true) AggregatorEnum aggregatorEnum, + @Parameter(description= "Type of downsample", example="1h-sum") @RequestParam(name="downsample",required = false) String downsample, + @Parameter(description= "Tags in json format", example="{\"id_spiral\":\"123\"}") @RequestParam(required = false) String tags) + { + + // It is not necessary validate the required params because spring already validates them. + // Neither is necessary validate than the date is greater than... because opentsdb already validates it. + + Response response; + try + { + Map<String,String> tagsMap = this.tagsToMap(city, tags); + tagsMap.put("city", city.name()); //we add manually the tag to the map so that the api remains homogeneous. + response = model.aggregate(city, metric, startDate, endDate, aggregatorEnum, downsample, tagsMap); + } + catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + return Utils.formatResponse( new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage())); + } + + return new ResponseEntity<Object>(response.getData(), new HttpHeaders(), HttpStatus.valueOf(response.getStatus().value())); + } + + private Map<String,String> tagsToMap (City city, String tags) + { //we add manually the tag to the map so that the api remains homogeneous. + Map<String,String> tagsMap = gson.fromJson(tags, Map.class); + + if ( tagsMap == null) + { + tagsMap = new HashMap<>(); + } + return tagsMap; + } +} \ No newline at end of file diff --git a/dataStorage/src/main/java/com/tecnalia/urbanite/storage/DataStorageApplication.java b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/DataStorageApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..3af0b7a82b6e52cd05548097dca805096f96823e --- /dev/null +++ b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/DataStorageApplication.java @@ -0,0 +1,54 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.context.annotation.Bean; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +//import io.swagger.v3.oas.models.info.Contact; +//import io.swagger.v3.oas.models.info.License; +//import io.swagger.v3.oas.models.ExternalDocumentation; + +@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) +public class DataStorageApplication { + + public static void main(String[] args) { + SpringApplication.run(DataStorageApplication.class, args); + } + + @Bean + public OpenAPI customOpenAPI(@Value("${application-description}") String appDesciption, @Value("${application-version}") String appVersion) { + return new OpenAPI() + .addServersItem(new Server().url("/data/")) + .info(new Info() + .title("Data Storage and Retrieval Component API") + .version(appVersion) + .description(appDesciption) + //.contact(new Contact().email("info@tecnalia.com").name("Tecnalia").url("tecnalia.com")) + //.termsOfService("http://swagger.io/terms/") + //.license(new License().name("Apache 2.0").url("http://springdoc.org")) + ) + //.externalDocs(new ExternalDocumentation().description("Data Model Explanation").url("/data/datamodel.html")) + ; + } +} diff --git a/dataStorage/src/main/java/com/tecnalia/urbanite/storage/MetadataAPI.java b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/MetadataAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..01d344e3adb95fd8fa61ebbe584af7267198480d --- /dev/null +++ b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/MetadataAPI.java @@ -0,0 +1,126 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.tecnalia.urbanite.storage.Utils.Utils; +import com.tecnalia.urbanite.storage.controllers.MetadataController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@RestController +@Tag(name = "Metadata", description = "Operations to store and retrieve metadata") +public class MetadataAPI { + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Add new metadata of a dataset or update if exists", description = "Adds new metadata of a dataset into the database, of updates the metadata if already exists for that id") + @RequestMapping( path = "/dataset", + method = RequestMethod.PUT, + produces = {"application/json"}) + public ResponseEntity<Object> insertDataset( + @Parameter(description= "Unique identifier of the metadata.", example="6bb9c361_177a635e86a") @RequestParam(required = true) String id, + @Parameter(description= "Metadata (JSON format).") @RequestBody (required = true) String metadata) { + + MetadataController controller = new MetadataController(); + APIResponse res = controller.insertMetadata(id, metadata); + //return new ResponseEntity<Object>(res.toString(), new HttpHeaders(), res.getStatus()); + return Utils.formatResponse(res); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Get metadata of a dataset", description = "Gets the metadata of a dataset from the database") + @RequestMapping( path = "/getDataset", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getDataset( + @Parameter(description= "Unique identifier of the metadata.", example="6bb9c361_177a635e86a") @RequestParam(required = true) String id) { + + MetadataController controller = new MetadataController(); + APIResponse res = controller.getDataset(id); + //return new ResponseEntity<Object>(res.toString(), new HttpHeaders(), res.getStatus()); + return Utils.formatResponse(res); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request."), + @ApiResponse(responseCode = "404", description = "Dataset not found.")}) + @Operation(summary = "Delete the metadata of a dataset.", description = "Delete the metadata of a dataset from the database") + @RequestMapping( path = "/dataset", + method = RequestMethod.DELETE, + produces = {"application/json"}) + public ResponseEntity<Object> deleteDataset( + @Parameter(description= "Unique identifier of the metadata.", example="6bb9c361_177a635e86a") @RequestParam(required = true) String id) { + + MetadataController controller = new MetadataController(); + APIResponse res = controller.deleteMetadata(id); + //return new ResponseEntity<Object>(res.toString(), new HttpHeaders(), res.getStatus()); + return Utils.formatResponse(res); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Get the metadata of all datasets.", description = "Gets the metadata of all the datasets stored in the database") + @RequestMapping( path = "/getCatalogueDatasets", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getCatalogueDatasets() { + + MetadataController controller = new MetadataController(); + APIResponse res = controller.getCatalogueDatasets(); + //return new ResponseEntity<Object>(res.toString(), new HttpHeaders(), res.getStatus()); + return Utils.formatResponse(res, true); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Searches among the metadata of the existing dataset", description = "Searches among the metadata of the existing dataset.") + @RequestMapping( path = "/searchDatasets", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> searchDatasets( + @Parameter(description = "Search tags.", example = "Bilbao Calendar") @RequestParam(required = false) String search) { + + APIResponse res = new APIResponse(); + + if (search == null || search.isEmpty()) + return getCatalogueDatasets(); + else { + MetadataController controller = new MetadataController(); + res = controller.searchDatasets(search); + //return new ResponseEntity<Object>(res.toString(), new HttpHeaders(), res.getStatus()); + return Utils.formatResponse(res, true); + } + } + + +} diff --git a/dataStorage/src/main/java/com/tecnalia/urbanite/storage/RetrievalAPI.java b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/RetrievalAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..583b4e6f9748100c362194a402521ad254dcc688 --- /dev/null +++ b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/RetrievalAPI.java @@ -0,0 +1,456 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.Utils.Utils; +import com.tecnalia.urbanite.storage.controllers.TrafficFlowObservedController; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@RestController +@Tag(name = "Data Retrieval", description = "Operations to retrieve Data") +public class RetrievalAPI { + + private Logger logger = LoggerFactory.getLogger(RetrievalAPI.class); + + @ApiResponse(responseCode = "200", description = "Successful operation.") + @Operation(summary = "Get available Data Models", description = "Returns all the Data Models that are currently implemented.") + @RequestMapping( path = "/getSupportedDataModels", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getSupportedDataModels() { + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + for (DataModel m: DataModel.values()){ + JSONObject mod = new JSONObject(); + try { + mod.put("id", m.getId()); + mod.put("name", m.getName()); + mod.put("description", m.getDescription()); + mod.put("reference", m.getReference()); + mod.put("example", m.getExample()); + lstRes.add(mod); + } catch (JSONException e) { + logger.error("RetrievalAPI::getDataModels::. Error creating JSON with model " + m.getName() + ": " + e.getMessage()); + } + } + APIResponse res = new APIResponse(HttpStatus.OK, lstRes); + return Utils.formatResponse(res, true); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Get data from the database within a specific time range", description = "Returns data of type {model} from the database of the city {city}, according to the specified time range,") + @RequestMapping( path = "/getTDataRange/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getTDataRange( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + res = model.getTDataRange(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request."), + @ApiResponse(responseCode = "404", description = "Document not found.")}) + @Operation(summary = "Get a record from the database", description = "Returns the record of type {model} identified by {id} from the database of the city {city}.") + @RequestMapping( path = "/getTData/{model}/{city}/{id}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getDataById( + @PathVariable DataModel model, + @PathVariable City city, + @PathVariable String id) { + + APIResponse res = model.getDataByID(city, id); + return Utils.formatResponse(res); + } + + @Operation(summary = "Get data from the database", description = "Returns data of type {model} from the database of the city {city}. Some filters can be applied to model fields.") + @RequestMapping( + path = "/getTData/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getTData( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description = "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + //check the filters are in JSON format + //System.out.println("filters: " + filters); + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + res = model.getTData(city, jsFilters, lstReturnFields, limit, sort); + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + + return Utils.formatResponse(res, true); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Get the different values for a specific field.", description = "Returns the different values of the field of type {model} from the database of the city {city}.") + @RequestMapping( path = "/getDistinct/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getDistinct( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description = "Data model field to return its different values.") @RequestParam(required = false) String field) { + + APIResponse res = new APIResponse(); + + if (field.trim().isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Field name can't be empty."); + } + else if (!field.contains(",")){ + res = model.getDistinct(city, field); + } + else { + res = model.getDistinct(city, field.split(",")); + } + + return Utils.formatResponse(res, false); + } + + + + + + + @Hidden + @RequestMapping( path = "/testNewGetTDataRange/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> testNewGetTDataRange( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + + TrafficFlowObservedController cont = new TrafficFlowObservedController(); + res = cont.getTDataRangeOtherMethod(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } + + + + @Hidden + @RequestMapping( path = "/testNewGetTDataRangeOther/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> testNewGetTDataRangeOtherOther( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + + TrafficFlowObservedController cont = new TrafficFlowObservedController(); + res = cont.getTDataRangeOtherMethod2(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } + + + @Hidden + @RequestMapping( path = "/testGetTDataRangeThreads/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> testGetTDataRangeThreads( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + + TrafficFlowObservedController cont = new TrafficFlowObservedController(); + res = cont.getTDataRangeThreadsMethod(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } +} diff --git a/dataStorage/src/main/java/com/tecnalia/urbanite/storage/StorageAPI.java b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/StorageAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..e23174b7e766b551536d51275aa0e740adfa3686 --- /dev/null +++ b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/StorageAPI.java @@ -0,0 +1,104 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.Utils.Utils; +import com.tecnalia.urbanite.storage.controllers.TrafficFlowObservedController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + + +@RestController +@Tag(name = "Data Storage", description = "Operations to store Data") +public class StorageAPI { + private final Logger logger = LoggerFactory.getLogger(TrafficFlowObservedController.class); + + private StorageAPI() { + //read configurations for databases + DBConfiguration.readDBsConfigurations(); + + } + + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Add new data to database", description = "Adds new data of type {model} to the database of the city {city}.") + @RequestMapping( path = "/insertTData/{model}/{city}", + method = RequestMethod.POST, + consumes = {"application/json"}, + produces = {"application/json"}) + public ResponseEntity<Object> insertTData( + @PathVariable DataModel model, + @PathVariable City city, + @RequestBody String data) + { + APIResponse response = model.insertData(city, data); + return Utils.formatResponse(response); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request."), + @ApiResponse(responseCode = "404", description = "Document not found.")}) + @Operation(summary = "Update one record of the database", description = "Updates a record (of type {model}) identified by {id} in the database of the city {city}.") + @RequestMapping( path = "/updateTData/{model}/{city}/{id}", + method = RequestMethod.PUT, + consumes = {"application/json"}, + produces = {"application/json"}) + public ResponseEntity<Object> updateTData( + @PathVariable DataModel model, + @PathVariable City city, + @PathVariable String id, + @RequestBody String data) + { + APIResponse response = model.updateData(city, id, data); + return Utils.formatResponse(response); + } + + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request."), + @ApiResponse(responseCode = "404", description = "Document not found.")}) + @Operation(summary = "Delete a single record from the database", description = "Deletes the record of type {model} identified by {id} from the database of the city {city}.") + @RequestMapping( path = "/deleteTData/{model}/{city}/{id}", + method = RequestMethod.DELETE, + produces = {"application/json"}) + public ResponseEntity<Object> deleteDataById( + @PathVariable DataModel model, + @PathVariable City city, + @PathVariable String id) + { + APIResponse response = model.deleteDataByID(city, id); + return Utils.formatResponse(response); + } +} diff --git a/dataStorage/src/main/java/com/tecnalia/urbanite/storage/appConfig.java b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/appConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..bab61abe30b44413e2066731a5775c5a439fce16 --- /dev/null +++ b/dataStorage/src/main/java/com/tecnalia/urbanite/storage/appConfig.java @@ -0,0 +1,36 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.tecnalia.urbanite.storage.Utils.StringToEnumConverter; + +@Configuration +public class appConfig implements WebMvcConfigurer +{ + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new StringToEnumConverter()); + } + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedMethods("*").allowedHeaders("*"); + } +} diff --git a/dataStorage/src/main/resources/application.properties b/dataStorage/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..4d49550045ba9524ef59c29fb86efd755695ab52 --- /dev/null +++ b/dataStorage/src/main/resources/application.properties @@ -0,0 +1,7 @@ +application-description=@project.description@ +application-version=@project.version@ +logging.level.org.springframework.boot.autoconfigure=ERROR +server.servlet.context-path=/data +server.port=80 +logging.level.com.tecnalia.urbanite.storage=DEBUG + diff --git a/dataStorage/src/main/resources/static/datamodels.html b/dataStorage/src/main/resources/static/datamodels.html new file mode 100644 index 0000000000000000000000000000000000000000..27f521d06575e03301dbc4c8386585e9fe46c53c --- /dev/null +++ b/dataStorage/src/main/resources/static/datamodels.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="ISO-8859-1"> +<title>URBANITE - Data Models</title> +</head> +<body> +<p> Data Model </p> +</body> +</html> \ No newline at end of file diff --git a/dataStorage/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java b/dataStorage/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eff4c40d3683ca586ccd3e136a19e2ee320265b6 --- /dev/null +++ b/dataStorage/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java @@ -0,0 +1,456 @@ +// +//package com.tecnalia.urbanite.storage.functional; +// +//import com.google.gson.*; +//import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +//import com.tecnalia.urbanite.storage.Utils.Utils; +//import org.apache.http.HttpResponse; +//import org.apache.http.client.methods.HttpDelete; +//import org.apache.http.client.methods.HttpGet; +//import org.apache.http.client.methods.HttpPost; +//import org.apache.http.client.methods.HttpPut; +//import org.apache.http.conn.ssl.NoopHostnameVerifier; +//import org.apache.http.conn.ssl.TrustAllStrategy; +//import org.apache.http.entity.StringEntity; +//import org.apache.http.impl.client.HttpClientBuilder; +//import org.apache.http.ssl.SSLContextBuilder; +//import org.apache.http.util.EntityUtils; +//import org.junit.jupiter.api.Assertions; +//import org.junit.jupiter.api.Test; +//import org.springframework.http.HttpStatus; +// +//import java.io.BufferedReader; +//import java.io.IOException; +//import java.io.InputStream; +//import java.io.InputStreamReader; +//import java.net.URLEncoder; +//import java.nio.charset.StandardCharsets; +//import java.security.KeyManagementException; +//import java.security.KeyStoreException; +//import java.security.NoSuchAlgorithmException; +//import java.text.DateFormat; +//import java.text.SimpleDateFormat; +//import java.time.Duration; +//import java.util.Date; +//import java.util.TimeZone; +//import java.util.concurrent.ThreadLocalRandom; +// +//public class StorageFunctionalTest +//{ +// private final static String INSERT_URI = getApiUrl() + "/data/insertTData/trafficFlowObserved/bilbao"; +// private final static String UPDATE_URI = getApiUrl() + "/data/updateTData/trafficFlowObserved/bilbao"; +// private final static String DELETE_URI = getApiUrl() + "/data/deleteTData/trafficFlowObserved/bilbao"; +// private final static String AGGREGATE_URI = getApiUrl()+ "/data/aggregate/trafficFlowObserved/bilbao"; +// +// private static final Gson gson = new GsonBuilder() +// .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) +// .create(); +// +// public static String getApiUrl() +// { +// return System.getenv("API_TEST_URL") != null ? System.getenv("API_TEST_URL") : "http://localhost"; +// } +// +// @Test +// public void insert_empty() +// { +// try +// { +// HttpPost post = new HttpPost( INSERT_URI); +// post.addHeader("content-type", "application/json"); +// post.setEntity(new StringEntity("{}")); +// HttpResponse response = HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( post ); +// +// Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode()); +// +// String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); +// Assertions.assertEquals("Input data is not in required format (list of 'Traffic Flow Observation' objects)", +// JsonParser.parseString(responseString).getAsJsonObject().get("Error").getAsString()); +// +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// @Test +// public void insert_as_not_array() +// { +// try +// { +// String template = readFromInputStream("/traffic-flow-observed-template.json"); +// HttpResponse response = this.buildPost(template); +// Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode()); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// +// //Inserto un elemento +// @Test +// public void insert_one_element_as_array() +// { +// try +// { +// // inserto +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //Inserto dos elementos +// @Test +// public void insert_two_elements_as_array() +// { +// try +// { +// String trafficElement1 = this.getTrafficElement(); +// String trafficElement2 = this.getTrafficElement(); +// +// String trafficElementAsArray = "["+ trafficElement1 + "," + trafficElement2+ "]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement1).getAsJsonObject().get("id").toString() + "}," +// + "{\"id\":" + JsonParser.parseString(trafficElement2).getAsJsonObject().get("id").toString()+ "}]", jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement1); +// this.assertIntensity(trafficElement2); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //Inserto un elemento y lo vuelvo a insertar +// @Test +// public void insert_one_element_as_array_again() +// { +// try +// { +// // inserto +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // inserto de nuevo +// trafficElement = this.modifyIntensity(trafficElement); +// trafficElementAsArray = "["+trafficElement+"]"; +// response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //update un elemento +// @Test +// public void update_one_element() +// { +// try +// { +// // primero lo inserto para poder hacer luego el update +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // hago el update +// trafficElement = this.modifyIntensity(trafficElement); +// response = this.buildPut(trafficElement); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals(JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").getAsInt(), +// jsonObject.get("updatedData").getAsJsonObject().get("intensity").getAsInt()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //delete un elemento +// @Test +// public void delete_one_element() +// { +// try +// { +// // primero lo inserto para poder hacer luego el delete +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // hago el delete +// response = this.buildDelete(trafficElement); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// Assertions.assertEquals(JsonParser.parseString(trafficElement).getAsJsonObject().get("id").getAsString(), +// jsonObject.get("deleted").getAsString()); +// +// Thread.sleep(3000); +// +// // Lo vuelvo a consultar y no estĂ¡ +// Date start = Date.from(new Date().toInstant().minus(Duration.ofMinutes(5))); +// String id_spiral = JsonParser.parseString(trafficElement).getAsJsonObject().get("name").toString(); +// response = this.buildQuery(start, new Date(), AggregatorEnum.SUM, "{\"id_spiral\":\"" + id_spiral +"\"}"); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// Assertions.assertEquals("[]",EntityUtils.toString(response.getEntity())); // empty +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// +// //Comprueba que funciona la agregaciĂ³n. +// @Test +// public void aggregate_elements_sum() +// { +// try +// { +// int intensitySum = 0; +// String startDate = ""; +// String endDate = ""; +// +// StringBuilder trafficElementAsArray = new StringBuilder(); +// trafficElementAsArray.append("["); +// int elements = ThreadLocalRandom.current().nextInt(0, 20); +// for ( int i = 1; i<= elements ; i++) +// { +// String trafficElement = this.getTrafficElement(String.valueOf(i)); // Pass the intensity to evict duplicated spirals. +// intensitySum += JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").getAsInt(); +// if ( i==1) +// { +// startDate = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").getAsString(); +// } +// else if ( i == elements) +// { +// endDate = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").getAsString(); +// } +// trafficElementAsArray.append(trafficElement); +// if ( i != elements ) // We add the colon only when no is the last iteration. +// { +// trafficElementAsArray.append(","); +// } +// Thread.sleep(100); +// } +// trafficElementAsArray.append("]"); +// +// HttpResponse response = this.buildPost(trafficElementAsArray.toString()); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Thread.sleep(3000); +// +// this.assertSumIntensity(Utils.ISO2Date(startDate), Utils.ISO2Date(endDate), intensitySum); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// private String getTrafficElement() throws IOException +// { +// return this.getTrafficElement(String.valueOf(ThreadLocalRandom.current().nextInt(0, 300))); +// } +// +// private String getTrafficElement(String idSpiral) throws IOException +// { +// String template = readFromInputStream("/traffic-flow-observed-template.json"); +// +// Date dateObserved = new Date(); +// SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm"); +// formatter.setTimeZone(TimeZone.getTimeZone("UTC")); +// String strDateObserved = formatter.format(dateObserved); +// String dateObservedFormatted = strDateObserved.replace("/","").replace(":","").replace(" ",""); +// +// DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); +// df.setTimeZone(TimeZone.getTimeZone("UTC")); +// template = template.replace("#dateObserved#",df.format(dateObserved)); +// template = template.replace("#dateObservedFormat#", dateObservedFormatted); +// template = template.replace("#intensity#", String.valueOf(ThreadLocalRandom.current().nextInt(0, 300))); +// template = template.replace("#id_spiral#",idSpiral); +// return template; +// } +// +// private HttpResponse buildPost(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// HttpPost post = new HttpPost( INSERT_URI); +// post.addHeader("content-type", "application/json"); +// post.setEntity(new StringEntity(data)); +// +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( post ); +// } +// +// private HttpResponse buildPut(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// String id = JsonParser.parseString(data).getAsJsonObject().get("id").getAsString(); +// HttpPut put = new HttpPut( UPDATE_URI + "/" + URLEncoder.encode(id, StandardCharsets.UTF_8.toString())); +// put.addHeader("content-type", "application/json"); +// put.setEntity(new StringEntity(data)); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( put ); +// } +// +// private HttpResponse buildDelete(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// String id = JsonParser.parseString(data).getAsJsonObject().get("id").getAsString(); +// HttpDelete delete = new HttpDelete( DELETE_URI + "/" + URLEncoder.encode(id, StandardCharsets.UTF_8.toString())); +// delete.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( delete ); +// } +// +// private HttpResponse buildQuery(Date start, Date end, AggregatorEnum aggregatorEnum, String tags) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Conversion +// SimpleDateFormat simpleDateFormat = new SimpleDateFormat( +// "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); +// simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); +// +// String startDate = simpleDateFormat.format(start); +// String endDate = simpleDateFormat.format(end); +// +// String query = String.format(AGGREGATE_URI + "/intensity?startDate=%s&endDate=%s&aggregator=%s&tags=%s",startDate, endDate, aggregatorEnum, URLEncoder.encode(tags, StandardCharsets.UTF_8.toString())); +// HttpGet get = new HttpGet(query); +// get.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( get ); +// } +// +// private HttpResponse buildQuery(Date start, Date end,String downsample, AggregatorEnum aggregatorEnum ) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Conversion +// SimpleDateFormat simpleDateFormat = new SimpleDateFormat( +// "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); +// simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); +// +// String startDate = simpleDateFormat.format(start); +// String endDate = simpleDateFormat.format(end); +// +// String query = String.format(AGGREGATE_URI + "/intensity?startDate=%s&endDate=%s&downsample=%s&aggregator=%s",startDate, endDate,downsample, aggregatorEnum); +// HttpGet get = new HttpGet(query); +// get.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( get ); +// } +// +// private String readFromInputStream(String path) +// throws IOException { +// InputStream inputStream = this.getClass().getResourceAsStream(path); +// StringBuilder resultStringBuilder = new StringBuilder(); +// try (BufferedReader br +// = new BufferedReader(new InputStreamReader(inputStream))) { +// String line; +// while ((line = br.readLine()) != null) { +// resultStringBuilder.append(line).append("\n"); +// } +// } +// return resultStringBuilder.toString(); +// } +// +// private void assertIntensity(String trafficElement) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Lo consulto +// Date start = Date.from(new Date().toInstant().minus(Duration.ofMinutes(5))); +// String id_spiral = JsonParser.parseString(trafficElement).getAsJsonObject().get("name").toString(); +// HttpResponse aggregatorResponse = this.buildQuery(start, new Date(), AggregatorEnum.SUM, "{\"id_spiral\":\"" + id_spiral +"\"}"); +// String result = EntityUtils.toString(aggregatorResponse.getEntity()); +// +// int intensityExpected = Integer.parseInt(JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").toString()); +// String dateObservedRecovery = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").toString().replace("\"",""); +// String dateObservedEpoch = String.valueOf(Utils.ISO2Date(dateObservedRecovery).getTime()/1000); +// int intensityActual = JsonParser.parseString(result).getAsJsonArray().get(0).getAsJsonObject().get("dps").getAsJsonObject().get(dateObservedEpoch).getAsInt(); +// +// Assertions.assertEquals(intensityExpected, intensityActual); +// } +// +// private void assertSumIntensity(Date start, Date end, int sumIntensityExpected) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// end = Date.from(end.toInstant().plus(Duration.ofMinutes(1))); +// HttpResponse aggregatorResponse = this.buildQuery(start, end, "all-sum", AggregatorEnum.SUM); +// String result = EntityUtils.toString(aggregatorResponse.getEntity()); +// +// String startEpoch = String.valueOf(start.getTime()/1000); +// int sumIntensityActual = JsonParser.parseString(result).getAsJsonArray().get(0).getAsJsonObject().get("dps").getAsJsonObject().get(startEpoch).getAsInt(); +// Assertions.assertEquals(sumIntensityExpected, sumIntensityActual); +// } +// +// private String modifyIntensity(String trafficElement) +// { +// JsonObject jsonObject1 = JsonParser.parseString(trafficElement).getAsJsonObject(); +// jsonObject1.remove("intensity"); +// int newIntensity = ThreadLocalRandom.current().nextInt(0, 300); +// jsonObject1.addProperty("intensity", newIntensity); +// trafficElement = jsonObject1.toString(); +// return trafficElement; +// } +//} +// diff --git a/dataStorage/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java b/dataStorage/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java new file mode 100644 index 0000000000000000000000000000000000000000..b80ee31695c75bc3f1673e72f9900074933e4210 --- /dev/null +++ b/dataStorage/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java @@ -0,0 +1,148 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.tools; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.tecnalia.urbanite.storage.Utils.Utils; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +/** + * export the trafficflowobserved from the database mongo db to database opentsdb. + */ +public class Export { + + private static final String GET_URL = "https://bilbao.urbanite.esilab.org/data/getTDataRange/trafficFlowObserved/bilbao"; + + public static void main(String[] args) throws IOException + { + LocalDateTime start = LocalDateTime.parse("2021-01-01T00:00:00.000Z",DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + LocalDateTime end = LocalDateTime.parse("2021-12-31T00:00:00.000Z",DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + File file = createFile("C:\\FicherosUrbanite\\traffic-2021.txt"); + BufferedWriter writer = new BufferedWriter(new FileWriter(file, true)); + + File errorsFile = createFile("C:\\FicherosUrbanite\\errors.txt"); + BufferedWriter errorWriter = new BufferedWriter(new FileWriter(errorsFile, true)); + for (LocalDateTime iterationDate = start; iterationDate.isBefore(end); iterationDate = iterationDate.plusDays(1)) + { + System.out.println(iterationDate); + Map<String,String> map = new HashMap<>(); + + try + { + + String queryStart = iterationDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + LocalDateTime queryEndDatetime = iterationDate.plusHours(23).plusMinutes(59).plusSeconds(59).plusNanos(999); + String queryEnd = queryEndDatetime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + String nameForFile = queryStart.replace("-","").replace("-","").replace(":","").replace(".",""); + + + + HttpGet get = new HttpGet(String.format(GET_URL + "?startDate=%s&endDate=%s&limit=100000&sort=ASC", URLEncoder.encode(queryStart, StandardCharsets.UTF_8.toString()), URLEncoder.encode(queryEnd, StandardCharsets.UTF_8.toString()))); + get.addHeader("content-type", "application/json"); + HttpResponse response = HttpClientBuilder.create().build().execute( get ); + if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) + { + String json = EntityUtils.toString(response.getEntity()); + + if ( json != null && !json.isEmpty()) + { + JsonArray array = JsonParser.parseString(json).getAsJsonArray(); + + if (array != null && array.size() > 0) + { + if ( file != null) + { + + for (JsonElement element : array) + { + String dateObserved = element.getAsJsonObject().get("dateObserved").getAsString(); + String idSpiral = element.getAsJsonObject().get("name").getAsString(); + String intensity = element.getAsJsonObject().get("intensity").getAsString(); + + String key = dateObserved+idSpiral; + if ( !map.containsKey(key)) + { + writer.write(String.format("trafficflowobserved.intensity %s %s city=bilbao id_spiral=%s\n",Utils.ISO2Date(dateObserved).getTime()/1000, intensity, idSpiral)); + map.put(key, intensity); + } + } + + }else + { + errorWriter.write("Date:" + start + ". It could not create the file."); + } + } + else + { + errorWriter.write("Date:" + start + ". The array from the get response is null or empty."); + } + }else + { + errorWriter.write("Date:" + start + ". The entity is null or empty."); + } + } + else + { + errorWriter.write("Date:" + start + ". The response is null or empty."); + } + } + catch (Exception ex) + { + try { + errorWriter.write("Date:" + start + ". It ocurred an exception. " + ex.getMessage()); + + } + catch (Exception e) + { + System.out.println("Error!!"); + } + + System.out.println("Error!!"); + } + } + errorWriter.close(); + writer.close(); + } + + + private static File createFile(String name) throws IOException + { + File file = new File(name); + if ( file.exists()) + { + file.delete(); + } + + return file.createNewFile() != false ? file : null; + } +} diff --git a/dataStorage/src/test/resources/traffic-flow-observed-template.json b/dataStorage/src/test/resources/traffic-flow-observed-template.json new file mode 100644 index 0000000000000000000000000000000000000000..b831cc245ae5a9b1d71b0d5d7333200aef0f1d8b --- /dev/null +++ b/dataStorage/src/test/resources/traffic-flow-observed-template.json @@ -0,0 +1,18 @@ +{ + "@context" : [ "https://smartdatamodels.org/context.jsonld", "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ], + "address" : { + "addressCountry" : "ES", + "addressLocality" : "Bilbao" + }, + "type" : "TrafficFlowObserved", + "location" : { + "coordinates" : [ [ [ 504417.9371092392, 4790030.564573327 ], [ 504453.27293873084, 4790030.564573327 ], [ 504453.27293873084, 4790133.21077546 ], [ 504417.9371092392, 4790133.21077546 ] ] ], + "type" : "Polygon" + }, + "dateObserved" : "#dateObserved#", + "occupancy" : 1, + "intensity" : #intensity#, + "averageVehicleSpeed" : 54, + "name" : #id_spiral#, + "id" : "urn:ngsi-ld:TrafficFlowObserved:#id_spiral#:#dateObservedFormat#" +} \ No newline at end of file diff --git a/openDataRetrieval/.gitignore b/openDataRetrieval/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b56f1dd0d04da4e03f710af3917e4fbdd9be4aa8 --- /dev/null +++ b/openDataRetrieval/.gitignore @@ -0,0 +1,33 @@ +README.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/openDataRetrieval/.mvn/wrapper/maven-wrapper.jar b/openDataRetrieval/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..c1dd12f17644411d6e840bd5a10c6ecda0175f18 Binary files /dev/null and b/openDataRetrieval/.mvn/wrapper/maven-wrapper.jar differ diff --git a/openDataRetrieval/.mvn/wrapper/maven-wrapper.properties b/openDataRetrieval/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..b74bf7fcd640440a49eb602158547670ef907772 --- /dev/null +++ b/openDataRetrieval/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/openDataRetrieval/Dockerfile b/openDataRetrieval/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b8d0941c868b923c1d88215fb2b4120383415f3a --- /dev/null +++ b/openDataRetrieval/Dockerfile @@ -0,0 +1,33 @@ +FROM maven:3.6.0-jdk-8-slim AS builder + +WORKDIR /home/app/shared + +RUN mkdir -p /home/app/shared + +COPY shared/pom.xml /home/app/shared +RUN mvn dependency:go-offline + +COPY shared/src /home/app/shared/src +RUN mvn install + +WORKDIR /home/app + +COPY pom.xml . +RUN mvn dependency:go-offline + +COPY src src/ +RUN mvn clean package + +FROM openjdk:8-slim + +WORKDIR /root + +COPY --from=builder /home/app/target/openDataRetrieval.jar /openDataRetrieval.jar + +ENV MONGO_HOST=mongodb \ + MONGO_PORT=27017 \ + MONGO_DBNAME=urbanite \ + OPENTSDB_URL=http://opentsdb:4242 + +EXPOSE 80 +CMD ["java","-jar","/openDataRetrieval.jar"] \ No newline at end of file diff --git a/openDataRetrieval/pom.xml b/openDataRetrieval/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..91ff1972542d26c7b430fcfb0922edb4261a01f2 --- /dev/null +++ b/openDataRetrieval/pom.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.7.3</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.tecnalia.urbanite</groupId> + <artifactId>openDataRetrieval</artifactId> + <version>1.0.0</version> + <name>openDataRetrieval</name> + <description>URBANITE Open Data Retrieval Component</description> + <properties> + <java.version>1.8</java.version> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.6.11</version> + </dependency> + <dependency> + <groupId>com.tecnalia.urbanite.storage</groupId> + <artifactId>shared</artifactId> + <version>1.0.0</version> + <scope>compile</scope> + </dependency> + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> diff --git a/openDataRetrieval/shared/.gitignore b/openDataRetrieval/shared/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7870c588b66e1a31f1cdbbacb3deef012fc86298 --- /dev/null +++ b/openDataRetrieval/shared/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +mvnw +mvnw.cmd + +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/openDataRetrieval/shared/.mvn/wrapper/MavenWrapperDownloader.java b/openDataRetrieval/shared/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000000000000000000000000000000000000..e76d1f3241d38db9b28f05133823bbed1ad289ff --- /dev/null +++ b/openDataRetrieval/shared/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/openDataRetrieval/shared/.mvn/wrapper/maven-wrapper.jar b/openDataRetrieval/shared/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 Binary files /dev/null and b/openDataRetrieval/shared/.mvn/wrapper/maven-wrapper.jar differ diff --git a/openDataRetrieval/shared/.mvn/wrapper/maven-wrapper.properties b/openDataRetrieval/shared/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..642d572ce90e5085986bdd9c9204b9404f028084 --- /dev/null +++ b/openDataRetrieval/shared/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/openDataRetrieval/shared/.mvn/wrapper/module-info.java b/openDataRetrieval/shared/.mvn/wrapper/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..485b276e70c65afcdfe1fac3b02f891c4f254d2d --- /dev/null +++ b/openDataRetrieval/shared/.mvn/wrapper/module-info.java @@ -0,0 +1,12 @@ +module gdsfg { + exports urbanite.prestojdbc; + exports urbanite.trafficmongopresto; + exports com.tecnalia.urbanite.storage; + exports com.tecnalia.urbanite.storage.transport.crowdflowobserved; + exports com.tecnalia.urbanite.storage.DB.Mongo; + exports com.tecnalia.urbanite.storage.transport.trafficflowobserved; + + requires java.logging; + requires java.sql; + requires org.apache.logging.log4j; +} \ No newline at end of file diff --git a/openDataRetrieval/shared/pom.xml b/openDataRetrieval/shared/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..9b144a297bb64cc708c5c8f1d0e21d90e8d11631 --- /dev/null +++ b/openDataRetrieval/shared/pom.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.4.2</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.tecnalia.urbanite.storage</groupId> + <artifactId>shared</artifactId> + <version>1.0.0</version> + <description>URBANITE Data Storage and Retrieval Component</description> + <properties> + <java.version>1.8</java.version> + <log4j2.version>2.17.0</log4j2.version> <!-- https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot --> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-ui</artifactId> + <version>1.2.32</version> + </dependency> + <!-- JSON --> + <dependency> + <groupId>org.codehaus.jettison</groupId> + <artifactId>jettison</artifactId> + <version>1.3.7</version> + <type>jar</type> + </dependency> + <!-- Jenna --> + <dependency> + <groupId>org.apache.jena</groupId> + <artifactId>apache-jena-libs</artifactId> + <version>3.17.0</version> + <type>pom</type> + </dependency> + <!-- Mongo --> + <dependency> + <groupId>org.mongodb</groupId> + <artifactId>mongodb-driver-sync</artifactId> + </dependency> + + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + </dependency> + + <!-- https://mvnrepository.com/artifact/javax.activation/activation --> + <dependency> + <groupId>javax.activation</groupId> + <artifactId>activation</artifactId> + <version>1.1</version> + </dependency> + + <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime --> + <dependency> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + </dependency> + + <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </dependency> + + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addClasspath>true</addClasspath> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + <executions> + <execution> + <id>assemble-all</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/OpentsdbClient.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/OpentsdbClient.java new file mode 100644 index 0000000000000000000000000000000000000000..4cd8aa569ac125183c2a7bdf9d754fefde5f74ff --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/OpentsdbClient.java @@ -0,0 +1,74 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client; + +import com.tecnalia.opentsdb.client.query.Query; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; + +public class OpentsdbClient implements Closeable +{ + final private String urlBase; + final private CloseableHttpClient httpClient = HttpClients.createDefault(); + + public OpentsdbClient(String urlBase) + { + this.urlBase = urlBase; + } + + public HttpResponse Post(Point point) throws IOException + { + HttpPost post = new HttpPost(MessageFormat.format("{0}/api/put/?summary", urlBase)); + post.addHeader("content-type", "application/json"); + post.setEntity(new StringEntity(point.toJson())); + return this.httpClient.execute(post); + } + + public HttpResponse Get(Query query) throws IOException + { + HttpPost get = new HttpPost(MessageFormat.format("{0}/api/query", urlBase)); + get.addHeader("content-type", "application/json"); + get.setEntity(new StringEntity(query.toJson())); + return this.httpClient.execute(get); + } + + public HttpResponse Delete(String metric, String city, String idSpiral, long timestamp) throws IOException + { + String url = urlBase + "/api/query?start=" + timestamp + "&end=" + (timestamp + 1) // add 1 ms because the end has to be greather than the start + + "&m=avg:" + metric + URLEncoder.encode("{city="+ city +",id_spiral="+ idSpiral +"}", StandardCharsets.UTF_8.toString()); + + HttpDelete delete = new HttpDelete(url); + delete.addHeader("content-type", "application/json"); + return this.httpClient.execute(delete); + } + + @Override + public void close() throws IOException + { + this.httpClient.close(); + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/Point.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/Point.java new file mode 100644 index 0000000000000000000000000000000000000000..3bf3925706973f4e741c255afbcfb821b13a05f3 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/Point.java @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.HashMap; +import java.util.Map; + +public class Point +{ + private final String metric; + private final long timestamp; + private final Object value; + private Map<String, String> tags = new HashMap<String, String>(); + + private static final Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + Point(PointBuilder builder) + { + this.metric = builder.metric; + this.timestamp = builder.timestamp; + this.value = builder.value; + this.tags = builder.tags; + } + + public String toJson() + { + return gson.toJson(this); + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/PointBuilder.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/PointBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..13016d6b36b504a51025840ef892e701d700dacc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/PointBuilder.java @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client; + +import java.util.HashMap; +import java.util.Map; + +public class PointBuilder +{ + public String metric; + public long timestamp; + public double value; + public Map<String, String> tags = new HashMap<String, String>(); + + public PointBuilder addMetric(String metric) { + this.metric = metric; + return this; + } + + public PointBuilder addTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + public PointBuilder addValue(double value) { + this.value = value; + return this; + } + + public PointBuilder addTag(String key, String value) { + this.tags.put(key,value); + return this; + } + + public Point build() { + Point point = new Point(this); + validate(point); + return point; + } + + private void validate(Point point) { + //Do some basic validations to check + //if user object does not break any assumption of system + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetriQueryBuilder.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetriQueryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..f59b0f67ca8adc06025b4b321fbb5e4661b8d438 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetriQueryBuilder.java @@ -0,0 +1,54 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import java.util.Map; + +public class MetriQueryBuilder +{ + public String aggregator; + public String metric; + public String downsample; + public Map<String,String> tags; + + public MetriQueryBuilder addAggregator(String aggregator) + { + this.aggregator = aggregator.toLowerCase(); + return this; + } + + public MetriQueryBuilder addMetric(String metric) + { + this.metric = metric; + return this; + } + + public MetriQueryBuilder addDownsample(String downsample) + { + this.downsample = downsample; + return this; + } + + public MetriQueryBuilder addTags(Map<String,String> tags) + { + this.tags = tags; + return this; + } + + public MetricQuery build() { + return new MetricQuery(this); + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetricQuery.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetricQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..e74f2e265f4dbab169c35f62fe084d3c47dcadc5 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/MetricQuery.java @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import java.util.Map; + +public class MetricQuery +{ + public String aggregator; + public String metric; + public String downsample; + public Map<String,String> tags; + + public MetricQuery (MetriQueryBuilder builder) + { + this.aggregator = builder.aggregator; + this.metric = builder.metric; + this.downsample = builder.downsample; + this.tags = builder.tags; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/Query.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/Query.java new file mode 100644 index 0000000000000000000000000000000000000000..365d8fb4d69398d5cccfedc44681e30137d56e86 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/Query.java @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.List; + +public class Query +{ + public long start; + public long end; + public List<MetricQuery> queries; + + public Query(QueryBuilder builder) + { + this.start = builder.start; + this.end = builder.end; + this.queries = builder.queries; + } + + private static final Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + public String toJson() + { + return gson.toJson(this); + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/QueryBuilder.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/QueryBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..c13b98aa4570cbcba299e8ff6263798288588fe8 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/opentsdb/client/query/QueryBuilder.java @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.opentsdb.client.query; + +import java.util.ArrayList; +import java.util.List; + +public class QueryBuilder +{ + public long start; + public long end; + public List<MetricQuery> queries; + + public QueryBuilder() + { + this.queries = new ArrayList<>(); + } + + + public QueryBuilder addStart(long start) + { + this.start = start; + return this; + } + + public QueryBuilder addEnd(long end) + { + this.end = end; + return this; + } + + public QueryBuilder addMetricQuery(MetricQuery query) + { + this.queries.add(query); + return this; + } + + public Query build() { + return new Query(this); + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/APIResponse.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/APIResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..49abd5c1796d553b6a4911eb4292ca495a7765bb --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/APIResponse.java @@ -0,0 +1,93 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.springframework.http.HttpStatus; + +public class APIResponse { + private HttpStatus status = HttpStatus.OK; + private String error = ""; + private List<JSONObject> data = new ArrayList<JSONObject>(); + + public APIResponse(HttpStatus status, String error, List<JSONObject> data) { + super(); + this.status = status; + this.error = error; + this.data = data; + } + + public APIResponse(HttpStatus status, String error) { + super(); + this.status = status; + this.error = error; + this.data = new ArrayList<JSONObject>(); + } + + public APIResponse() { + super(); + } + + public APIResponse(HttpStatus status, List<JSONObject> data) { + super(); + this.status = status; + this.error = ""; + this.data = data; + } + + public HttpStatus getStatus() { + return status; + } + public void setStatus(HttpStatus status) { + this.status = status; + } + public String getError() { + return error; + } + public void setError(String error) { + this.error = error; + } + public List<JSONObject> getData() { + return data; + } + public void setData(List<JSONObject> data) { + this.data = data; + } + + @Override + public String toString() { + JSONObject res = new JSONObject(); + try { + res.put("status", status); + if (error.isEmpty() == false) + res.put("error", error); + else + res.put("data", data); + return res.toString(); + } catch (JSONException e) { + e.printStackTrace(); + } + + return "{\"status\":\"" + status + "\"}"; + } + + + +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/CustomRestExceptionHandler.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/CustomRestExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..6d5d4cb470d056957ad615fffb63577c3fedeec6 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/CustomRestExceptionHandler.java @@ -0,0 +1,202 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + + +import java.util.ArrayList; + +import java.util.List; + + +import javax.validation.ConstraintViolation; + +import javax.validation.ConstraintViolationException; + + +import com.tecnalia.urbanite.storage.APIResponse; +import org.springframework.http.HttpHeaders; + +import org.springframework.http.HttpStatus; + +import org.springframework.http.ResponseEntity; + +import org.springframework.validation.BindException; + +import org.springframework.validation.FieldError; + +import org.springframework.validation.ObjectError; + +import org.springframework.web.bind.MethodArgumentNotValidException; + +import org.springframework.web.bind.MissingServletRequestParameterException; + +import org.springframework.web.bind.annotation.ControllerAdvice; + +import org.springframework.web.bind.annotation.ExceptionHandler; + +import org.springframework.web.context.request.WebRequest; + +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + + +import com.tecnalia.urbanite.storage.Utils.Utils; + + +@ControllerAdvice + +public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler { + + + + @Override + + protected ResponseEntity<Object> handleMethodArgumentNotValid( + + MethodArgumentNotValidException ex, + + HttpHeaders headers, + + HttpStatus status, + + WebRequest request) { + + + List<String> errors = new ArrayList<String>(); + + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + + errors.add(error.getField() + ": " + error.getDefaultMessage()); + + } + + for (ObjectError error : ex.getBindingResult().getGlobalErrors()) { + + errors.add(error.getObjectName() + ": " + error.getDefaultMessage()); + + } + + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, errors.get(0)); + + return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request); + + } + + + @Override + + protected ResponseEntity<Object> handleBindException( + + BindException ex, + + HttpHeaders headers, + + HttpStatus status, + + WebRequest request) { + + + List<String> errors = new ArrayList<String>(); + + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + + errors.add(error.getField() + ": " + error.getDefaultMessage()); + + } + + for (ObjectError error : ex.getBindingResult().getGlobalErrors()) { + + errors.add(error.getObjectName() + ": " + error.getDefaultMessage()); + + } + + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, errors.get(0)); + + return handleExceptionInternal(ex, apiError, headers, status, request); + + + } + + + @ExceptionHandler({ ConstraintViolationException.class }) + + public ResponseEntity<Object> handleConstraintViolation( + + ConstraintViolationException ex, WebRequest request) { + + + List<String> errors = new ArrayList<String>(); + + for (ConstraintViolation<?> violation : ex.getConstraintViolations()) { + + errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": " + violation.getMessage()); + + } + + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, errors.get(0)); + + //return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus()); + + return Utils.formatResponse(apiError, true); + + } + + + + @ExceptionHandler({ MethodArgumentTypeMismatchException.class }) + + public ResponseEntity<Object> handleMethodArgumentTypeMismatch( + + MethodArgumentTypeMismatchException ex, + + WebRequest request) { + + + String error = "Invalid value '" + ex.getValue() + "' for parameter '" + ex.getName() +"'"; + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, error); + + //return new ResponseEntity<Object>(apiError.toString(), new HttpHeaders(), apiError.getStatus()); + + return Utils.formatResponse(apiError, true); + + } + + + @Override + + protected ResponseEntity<Object> handleMissingServletRequestParameter( + + MissingServletRequestParameterException ex, + + HttpHeaders headers, HttpStatus status, WebRequest request) { + + String error = "Parameter '" + ex.getParameterName() + "' is required."; + + APIResponse apiError = new APIResponse(HttpStatus.BAD_REQUEST, error); + + //return new ResponseEntity<Object>(apiError.toString(), new HttpHeaders(), apiError.getStatus()); + + return Utils.formatResponse(apiError, true); + + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/DBConfiguration.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/DBConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..50c2b31191574fb2a06b0ed21ab4228ef20b02f2 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/DBConfiguration.java @@ -0,0 +1,140 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DB; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class DBConfiguration { + + public enum DBTYPE { + MONGODB + /* + MYSQL + */ + } + private DBConfiguration() {} + + private static final Map<DBTYPE, DBParams> dbConfigurationsMap = new HashMap<>(); + private static final Logger logger = LoggerFactory.getLogger(DBConfiguration.class); + + public static void readDBsConfigurations(){ + + //mongoDB + String mongoHost = System.getenv("MONGO_HOST"); + if (mongoHost == null) { + logger.warn("Environment variable MONGO_HOST not found --> setting to default (\"localhost\")"); + mongoHost = "localhost"; + } + String mongoPort = System.getenv("MONGO_PORT"); + if (mongoPort == null) { + logger.warn("Environment variable MONGO_PORT not found --> setting to default (27017)"); + mongoPort = "27017"; + } + String mongoDBName= System.getenv("MONGO_DBNAME"); + if (mongoDBName == null) { + logger.warn("Environment variable MONGO_DBNAME not found --> setting to default (\"urbanite\")"); + mongoDBName = "urbanite"; + } + + DBParams params = new DBParams(mongoHost, mongoPort, "", "", mongoDBName); + dbConfigurationsMap.put(DBTYPE.MONGODB, params); + logger.debug("MongoDB connection settings: Server: " + mongoHost + ":" + mongoPort + "\t\tDataBase: " + mongoDBName); + + //other databases + + } + + public static DBParams getDBConfiguration(DBTYPE dbType) { + if (dbConfigurationsMap.get(dbType) == null) + //read it again (just in case it hadn't been done yet) + readDBsConfigurations(); + + return dbConfigurationsMap.get(dbType); + } + + public static String getOpentsdbUrl() + { + return System.getenv("OPENTSDB_URL") != null ? System.getenv("OPENTSDB_URL") : "http://localhost:4242"; + } + + public static class DBParams { + + private String host; + private String port; + private String user; + private String pass; + private String dbName; + + public DBParams() { + + } + + public DBParams(String host, String port, String user, String pass, String dbName) { + super(); + this.host = host; + this.port = port; + this.user = user; + this.pass = pass; + this.dbName = dbName; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getPort() { + return port; + } + + public void setPort(String port) { + this.port = port; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPass() { + return pass; + } + + public void setPass(String pass) { + this.pass = pass; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBManager.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBManager.java new file mode 100644 index 0000000000000000000000000000000000000000..12fc42f488face47dbf9ba7b3a9211a10fbc93dc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBManager.java @@ -0,0 +1,154 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DB; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.connection.ServerConnectionState; +import com.mongodb.connection.ServerDescription; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class MongoDBManager implements Closeable +{ + private final Logger logger = LoggerFactory.getLogger(MongoDBManager.class); + + private MongoClient mongoClient = null; + private MongoDatabase database = null; + private String mongoServer= ""; + private String mongoDBName = ""; + private MongoClientSettings clientSettings = null; + + public MongoDBManager(DBConfiguration.DBParams params) { + String host = params.getHost(); + String port = params.getPort(); + this.mongoServer = host + ":" + port; + //ignore user and pass + this.mongoDBName = params.getDbName(); + this.clientSettings = MongoClientSettings.builder().applyConnectionString(new ConnectionString("mongodb://" + mongoServer)) + .applyToSocketSettings(builder -> + builder.connectTimeout(120, java.util.concurrent.TimeUnit.SECONDS) + .readTimeout(120, java.util.concurrent.TimeUnit.SECONDS)) + .applyToClusterSettings(builder -> + builder.serverSelectionTimeout(120, java.util.concurrent.TimeUnit.SECONDS)) + .applyToConnectionPoolSettings(builder -> + builder.maxConnectionIdleTime(180, java.util.concurrent.TimeUnit.SECONDS)) + .build(); + } + + public boolean connect() { + + try { + mongoClient = MongoClients.create(clientSettings); + + //mongoClient = MongoClients.create("mongodb://" + mongoServer); + boolean connected = false; + boolean error = false; + while (!connected && !error){ + Thread.sleep(100); + List<ServerDescription> servers = mongoClient.getClusterDescription().getServerDescriptions(); + for (ServerDescription srv : servers) { + if (srv.getState() == ServerConnectionState.CONNECTED) connected = true; + if (srv.getException() != null) error = true; + } + + } + if (error) { + logger.error("Can't connect to server " + mongoServer); + } + if (connected) { + database = mongoClient.getDatabase(mongoDBName); + return true; + } + } catch (Exception e) { + logger.error("Can't connect to server " + mongoServer + " - " + e.getMessage()); + } + return false; + + } + + public void close() { + if (mongoClient != null) + mongoClient.close(); + } + + public MongoDatabase getDatabase() { + return database; + } + + public List<String> getModelCollectionNames(String collNamePrefix,SortingMode sort){ + List<String> collectionNames = new ArrayList<String>(); + + MongoIterable<String> colNames = database.listCollectionNames(); + + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix.toLowerCase())) + collectionNames.add(collectionName.toLowerCase()); + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(collectionNames); + else + Collections.sort(collectionNames, Collections.reverseOrder()); + return collectionNames; + } + + public List<String> getModelCollectionNamesBetweenYears(String collNamePrefix,Date startDate, Date endDate, SortingMode sort){ + List<String> collectionNames = new ArrayList<String>(); + + MongoIterable<String> colNames = database.listCollectionNames(); + int startYear = Utils.getDateYear(startDate); + int endYear = Utils.getDateYear(endDate); + + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix.toLowerCase())) { + try { + int year = Integer.parseInt(collectionName.substring(collNamePrefix.length())); + //no errors --> check if it's in range + boolean addToList = true; + if (startYear > 0 && year < startYear) addToList = false; + if (endYear > 0 && year > endYear) addToList = false; + if (addToList) + collectionNames.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR" format --> omit table + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(collectionNames); + else + Collections.sort(collectionNames, Collections.reverseOrder()); + return collectionNames; + } + + +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBUtils.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d3246657896cc561a5156d492d5fbf945ed31ce9 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DB/MongoDBUtils.java @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DB; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.conversions.Bson; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mongodb.BasicDBObject; +import com.mongodb.client.model.Projections; + + +public class MongoDBUtils { + + + private static Logger logger = LoggerFactory.getLogger(MongoDBUtils.class); + + public static void addDateRangeFilter(BasicDBObject queryFilters, Date startDate, Date endDate, String dateSortAndRangeField) { + + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append(dateSortAndRangeField, dateRange); + } + + public static void addDateJSonFilter(BasicDBObject queryFilters, JSONObject filters) { + + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("MongoDBUtils::addDateJSonFilter::Error creating filters [" + filters + "]: " + e.getMessage()); + } + } + + public static Bson addReturnFields(List<String> returnFields,String dateSortAndRangeField ) { + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObservedFrom + if (returnFields.contains(dateSortAndRangeField) == false) returnFields.add(dateSortAndRangeField); + projection = Projections.fields(Projections.include(returnFields)); + } + return projection; + + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/AggregatorEnum.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/AggregatorEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..7d25d95d27f3b7472a754d0251290865153a7ee8 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/AggregatorEnum.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum AggregatorEnum +{ + MIN, + SUM, + MAX, + AVG, + DEV +} + + diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/City.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/City.java new file mode 100644 index 0000000000000000000000000000000000000000..183c27e1c8c0877ab33d6f1e6eb62b9e3ac5f259 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/City.java @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +import org.apache.commons.lang3.StringUtils; + +public enum City { + bilbao, + messina, + helsinki, + amsterdam; + + public static Boolean any(String id) + { + if(!StringUtils.isBlank(id)) + { + for(City city : City.values()) + { + if(id.equals(city.name())) + { + return true; + } + } + } + return false; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Address.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Address.java new file mode 100644 index 0000000000000000000000000000000000000000..c5a90fd26fe87056dc82d19c50c6fbcf06f7d5cd --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Address.java @@ -0,0 +1,108 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Address { + + @Schema(description = "The country. For example, Spain.", required = false, example = "") + private String addressCountry; + + @Schema(description = "The locality in which the street address is, and which is in the region.", required = false, example = "") + private String addressLocality; + + @Schema(description = "The region in which the locality is, and which is in the country.", required = false, example = "") + private String addressRegion; + + @Schema(description = "The geographic area where a service or offered item is provided.", required = false, example = "") + private String areaServed; + + @Schema(description = "The post office box number for PO box addresses.", required = false, example = "") + private String postOfficeBoxNumber; + + @Schema(description = "The postal code.", required = false, example = "") + private String postalCode; + + @Schema(description = "The street address.", required = false, example = "") + private String streetAddress; + + public String getAddressCountry() { + return addressCountry; + } + + public void setAddressCountry(String addressCountry) { + this.addressCountry = addressCountry; + } + + public String getAddressLocality() { + return addressLocality; + } + + public void setAddressLocality(String addressLocality) { + this.addressLocality = addressLocality; + } + + public String getAddressRegion() { + return addressRegion; + } + + public void setAddressRegion(String addressRegion) { + this.addressRegion = addressRegion; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getPostOfficeBoxNumber() { + return postOfficeBoxNumber; + } + + public void setPostOfficeBoxNumber(String postOfficeBoxNumber) { + this.postOfficeBoxNumber = postOfficeBoxNumber; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getStreetAddress() { + return streetAddress; + } + + public void setStreetAddress(String streetAddress) { + this.streetAddress = streetAddress; + } + + @Override + public String toString() { + return "{\"addressCountry\":\"" + addressCountry + "\", \"addressLocality\":\"" + addressLocality + + "\", \"addressRegion\":\"" + addressRegion + "\", \"areaServed\":\"" + areaServed + + "\", \"postOfficeBoxNumber\":\"" + postOfficeBoxNumber + "\", \"postalCode\":\"" + postalCode + + "\", \"streetAddress\":\"" + streetAddress + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Audience.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Audience.java new file mode 100644 index 0000000000000000000000000000000000000000..029e18442e7f39fa165139b0ecd3e8a36c3e8de6 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Audience.java @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum Audience { + adult, + allPublic, + children, + family, + senior, + teenager +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Category.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Category.java new file mode 100644 index 0000000000000000000000000000000000000000..f69661cc172409f0eedf1b263cdbeb6844c4f2bb --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Category.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum Category { + excursion, + gastronomy, + history, + museum, + outdoorActivities, + parksAndGardens, + religiousWorship, + shopping, + wellness +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CommonDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CommonDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..27da3185ce5d2e8bf9223708901c3b52425864b9 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CommonDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum CommonDataModel { + METADATA +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ContactPoint.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ContactPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..891a48fa00f1a418eb0fb4d248cc6cc1c393f434 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ContactPoint.java @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ContactPoint { + @Schema(description = "Property. Contact type of this item.", required = false, example = "") + private String contactType; + + @Schema(description = "Property.Email address of owner.", required = false, example = "") + private String email; + + @Schema(description = "Property. The name of this item.", required = false, example = "") + private String name; + + @Schema(description = "Property. Telephone of this contact.", required = false, example = "") + private String telephone; + + @Schema(description = "Property. URL which provides a description or further information about this item.", required = false, example = "") + private String url; + + public String getContactType() { + return contactType; + } + + public void setContactType(String contactType) { + this.contactType = contactType; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTelephone() { + return telephone; + } + + public void setTelephone(String telephone) { + this.telephone = telephone; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + @Override + public String toString() { + return "{\"contactType\":\"" + contactType + "\", \"email\":\"" + email + + "\", \"name\":\"" + name + "\", \"telephone\":\"" + telephone + + "\", \"url\":\"" + url + "\"}"; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CriticReview.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CriticReview.java new file mode 100644 index 0000000000000000000000000000000000000000..713839ea0675d7dfa09de9be5ca9e5f05c01bc03 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/CriticReview.java @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class CriticReview { + @Schema(description = "", required = false) + private String language; + + @Schema(description = "", required = false) + private List<Review> reviews; + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public List<Review> getReviews() { + return reviews; + } + + public void setReviews(List<Review> reviews) { + this.reviews = reviews; + } + @Override + public String toString() { + return "{\"language\":\"" + language + "\", \"reviews\":\"" + reviews + + "\"}"; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Currency.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Currency.java new file mode 100644 index 0000000000000000000000000000000000000000..f376fca910d4614bcdc8e7f29faaac903b83817f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Currency.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum Currency { + EUR, + USD +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DayOfWeek.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DayOfWeek.java new file mode 100644 index 0000000000000000000000000000000000000000..3c8a149c397c217a5ea64df470fdf303753a51d8 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DayOfWeek.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum DayOfWeek { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + Sunday, + PublicHolidays +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Dimension.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Dimension.java new file mode 100644 index 0000000000000000000000000000000000000000..ab4eee62a8b85d4ad1fed26230c553f91a45956d --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Dimension.java @@ -0,0 +1,58 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Dimension { + @Schema(required = false) + private Double depth; + + @Schema(required = false) + private Double height; + + @Schema(required = false) + private Double width; + + public Double getDepth() { + return depth; + } + + public void setDepth(Double depth) { + this.depth = depth; + } + + public Double getHeight() { + return height; + } + + public void setHeight(Double height) { + this.height = height; + } + + public Double getWidth() { + return width; + } + + public void setWidth(Double width) { + this.width = width; + } + + @Override + public String toString() { + return "{\"depth\":" + depth + ", \"height\":" + height + ", \"width\":" + width+ "}"; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/District.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/District.java new file mode 100644 index 0000000000000000000000000000000000000000..fb7dc0eac59660aaa3f414b9f20592bcbfb143bc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/District.java @@ -0,0 +1,78 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.HashMap; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class District { + + @Schema(description = "residents", required = false) + private DoubleValue residents; + + @Schema(description = "families", required = false) + private DoubleValue families; + + @Schema(description = "males", required = false) + private DoubleValue males; + + @Schema(description = "females", required = false) + private DoubleValue females; + + @Schema(description = "ages", required = false) + private HashMap<String, MaleFemale> ages; + + public DoubleValue getResidents() { + return residents; + } + + public void setResidents(DoubleValue residents) { + this.residents = residents; + } + + public DoubleValue getFamilies() { + return families; + } + + public void setFamilies(DoubleValue families) { + this.families = families; + } + + public DoubleValue getMales() { + return males; + } + + public void setMales(DoubleValue males) { + this.males = males; + } + + public DoubleValue getFemales() { + return females; + } + + public void setFemales(DoubleValue females) { + this.females = females; + } + + public HashMap<String, MaleFemale> getAges() { + return ages; + } + + public void setAges(HashMap<String, MaleFemale> ages) { + this.ages = ages; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DoubleValue.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DoubleValue.java new file mode 100644 index 0000000000000000000000000000000000000000..104004661f0792b4701eaa37cf2114bfe7c103cf --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/DoubleValue.java @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class DoubleValue { + + @Schema(description = "number, percentage,..", required = false) + private String type; + + @Schema(description = "", required = false) + private Double value; + + @Schema(description = "inhabitants/km^2,...", required = false) + private String unit; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ElectricTransport.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ElectricTransport.java new file mode 100644 index 0000000000000000000000000000000000000000..442d62aabdad6aa108a7f24cedfe39e0db80f430 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/ElectricTransport.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum ElectricTransport { + electricBicycle, + electricCar, + electricMotorBike, + electricScooter +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/InstallationMode.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/InstallationMode.java new file mode 100644 index 0000000000000000000000000000000000000000..314cb964079d53639b605679d04b6303e5824c88 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/InstallationMode.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum InstallationMode { + aerial, + ground, + underGround, + underSea +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Inventory.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Inventory.java new file mode 100644 index 0000000000000000000000000000000000000000..8c304a8570631f02cb34f1f9330c00e006124f4c --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Inventory.java @@ -0,0 +1,77 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Inventory { + @Schema(required = false) + private List<PlatformType> items; + + @Schema(required = false) + private Double nbOfIOPoint; + + @Schema(required = false) + private Double nbOfLane; + + @Schema(required = false) + private Double nbOfPlatform; + + public List<PlatformType> getItems() { + return items; + } + + public void setItems(List<PlatformType> items) { + this.items = items; + } + + public Double getNbOfIOPoint() { + return nbOfIOPoint; + } + + public void setNbOfIOPoint(Double nbOfIOPoint) { + this.nbOfIOPoint = nbOfIOPoint; + } + + public Double getNbOfLane() { + return nbOfLane; + } + + public void setNbOfLane(Double nbOfLane) { + this.nbOfLane = nbOfLane; + } + + public Double getNbOfPlatform() { + return nbOfPlatform; + } + + public void setNbOfPlatform(Double nbOfPlatform) { + this.nbOfPlatform = nbOfPlatform; + } + @Override + public String toString() { + String inventory ="{\"PlatformType\":["; + for (PlatformType pt:items) inventory=inventory+"\""+pt+"\","; + inventory = inventory.substring(0, inventory.length()-1); + inventory = inventory+"], \"nbOfIOPoint\":"+nbOfIOPoint+",\"nbOfLane\":"+nbOfLane+",\"nbOfPlatform\":"+nbOfPlatform+"\"}"; + return inventory; + + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Itinerary.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Itinerary.java new file mode 100644 index 0000000000000000000000000000000000000000..e2ea011b5d742dacf0c3659a26cf99fc3681a558 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Itinerary.java @@ -0,0 +1,78 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Itinerary { + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "", required = false) + private String image; + + @Schema(description = "", required = false) + private String name; + + @Schema(description = "", required = false) + private Integer position; + + @Schema(description = "", required = false) + private List<String>streetAddress; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(Integer position) { + this.position = position; + } + + public List<String> getStreetAddress() { + return streetAddress; + } + + public void setStreetAddress(List<String> streetAddress) { + this.streetAddress = streetAddress; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationGeojson.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationGeojson.java new file mode 100644 index 0000000000000000000000000000000000000000..d96a644b30768a1d6148356c5eba5db4efc04353 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationGeojson.java @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public abstract class LocationGeojson { + + private List<Double> bbox; + private String type; + + + public List<Double> getBbox() { + return bbox; + } + public void setBbox(List<Double> bbox) { + this.bbox = bbox; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public LocationGeojson() { + super(); + } + + @Override + public String toString() { + String locationGeojson ="{\"bbox\":["; + for ( Double pt:bbox) locationGeojson=locationGeojson+""+pt+","; + locationGeojson = locationGeojson.substring(0, locationGeojson.length()-1); + locationGeojson = locationGeojson+"], \"type\":\""+type+"\"}"; + return locationGeojson; + + + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationLineString.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationLineString.java new file mode 100644 index 0000000000000000000000000000000000000000..3bd9128860fe5fe8d902c6b024bf4d5679387faf --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationLineString.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationLineString extends LocationGeojson{ + + private List<List<Double>> coordinates; + + public LocationLineString() { + super(); + this.setType("LineString"); + } + + public List<List<Double>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<Double>> coordinates) { + this.coordinates = coordinates; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiLineString.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiLineString.java new file mode 100644 index 0000000000000000000000000000000000000000..49a9704f8c5bcc762e557e6190bfe89a77a1f33f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiLineString.java @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationMultiLineString extends LocationGeojson{ + + private List<List<List<Double>>> coordinates; + + public LocationMultiLineString() { + super(); + this.setType("MultiLineString"); + } + + public List<List<List<Double>>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<List<Double>>> coordinates) { + this.coordinates = coordinates; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPoint.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..7f1dcc3bba0dc7e4a71916c947202564ac524020 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPoint.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationMultiPoint extends LocationGeojson{ + + private List<List<Double>> coordinates; + + public LocationMultiPoint() { + super(); + this.setType("MultiPoint"); + } + + public List<List<Double>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<Double>> coordinates) { + this.coordinates = coordinates; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPolygon.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPolygon.java new file mode 100644 index 0000000000000000000000000000000000000000..5b016f43b82ba7d0c548822c8d2fba86e3e0d794 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationMultiPolygon.java @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationMultiPolygon extends LocationGeojson{ + + private List<List<List<List<Double>>>> coordinates; + + public LocationMultiPolygon() { + super(); + this.setType("MultiPolygon"); + } + + public List<List<List<List<Double>>>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<List<List<Double>>>> coordinates) { + this.coordinates = coordinates; + } + + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPoint.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..e71ebbb2ac41183720bc1fed827e7e47cb7eb069 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPoint.java @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationPoint extends LocationGeojson{ + + private List<Double> coordinates; + + public LocationPoint() { + super(); + this.setType("Point"); + } + + public List<Double> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<Double> coordinates) { + this.coordinates = coordinates; + } + + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPolygon.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPolygon.java new file mode 100644 index 0000000000000000000000000000000000000000..de52b18c907dbcd2936f6dcdc66b34e261508259 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/LocationPolygon.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +public class LocationPolygon extends LocationGeojson{ + + private List<List<List<Double>>> coordinates; + + public LocationPolygon() { + super(); + this.setType("Polygon"); + } + + public List<List<List<Double>>> getCoordinates() { + return coordinates; + } + + public void setCoordinates(List<List<List<Double>>> coordinates) { + this.coordinates = coordinates; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/MaleFemale.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/MaleFemale.java new file mode 100644 index 0000000000000000000000000000000000000000..f23f639a6a9ba183ac8b851d92094e3febc2765c --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/MaleFemale.java @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import com.google.gson.annotations.SerializedName; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class MaleFemale { + @SerializedName("M") + @Schema(description = "", name = "M",required = false) + private Double M; + + @Schema(description = "", name = "F",required = false) + @SerializedName("F") + private Double F; + + public Double getM() { + return M; + } + public void setM(Double m) { + M = m; + } + public Double getF() { + return F; + } + public void setF(Double f) { + F = f; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/OpeningHoursSpecification.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/OpeningHoursSpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..05eb1e2b5779e5fe1f329c3ea1e1215ec08e970e --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/OpeningHoursSpecification.java @@ -0,0 +1,83 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class OpeningHoursSpecification { + @Schema(description = "", required = false) + private String closes; + + @Schema(description = "", required = false) + private DayOfWeek dayOfWeek; + + @Schema(description = "", required = false) + private String opens; + + @Schema(description = "", required = false) + private String validFrom; + + @Schema(description = "", required = false) + private String validThrough; + + public String getCloses() { + return closes; + } + + public void setCloses(String closes) { + this.closes = closes; + } + + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + public String getOpens() { + return opens; + } + + public void setOpens(String opens) { + this.opens = opens; + } + + public String getValidFrom() { + return validFrom; + } + + public void setValidFrom(String validFrom) { + this.validFrom = validFrom; + } + + public String getValidThrough() { + return validThrough; + } + + public void setValidThrough(String validThrough) { + this.validThrough = validThrough; + } + @Override + public String toString() { + + String retorno= "{\"closes\":" +closes + ", \"dayOfWeek\":\"" + dayOfWeek + + "\", \"opens\":\"" + opens + "\", \"validFrom\":" + validFrom + + ", \"validThrough\":\"" + validThrough + "\"}"; + return retorno; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PaymentAccepted.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PaymentAccepted.java new file mode 100644 index 0000000000000000000000000000000000000000..721f62206dda38adac23052cd8c0a82e88393681 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PaymentAccepted.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum PaymentAccepted { + Cash, + CreditCard, + CryptoCurrency, + other +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Pitch.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Pitch.java new file mode 100644 index 0000000000000000000000000000000000000000..4572d0e17a08b3f4f5acfc724863c6e9e9268e15 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Pitch.java @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Pitch { + + @Schema(description = "", required = false) + private String article; + + @Schema(description = "", required = false) + private String language; + + public String getArticle() { + return article; + } + + public void setArticle(String article) { + this.article = article; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PlatformType.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PlatformType.java new file mode 100644 index 0000000000000000000000000000000000000000..ed9f8812b20dd984964f0d44398127cecb10ba5d --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PlatformType.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum PlatformType { + lateral, + central +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PopulationSummary.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PopulationSummary.java new file mode 100644 index 0000000000000000000000000000000000000000..700dc3eafab64e0c908df4977ee1affcf9e239c3 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PopulationSummary.java @@ -0,0 +1,76 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PopulationSummary { + + @Schema(description = "", name = "population-summary",required = false) + private DoubleValue metropolitanAareaInahitants; + + @Schema(description = "", name = "city-inhabitants",required = false) + private DoubleValue cityInhabitants; + + @Schema(description = "", name = "population-density",required = false) + private DoubleValue populationDensity; + + @Schema(description = "", required = false) + private DoubleValue males; + + @Schema(description = "",required = false) + private DoubleValue females; + + public DoubleValue getMetropolitanAareaInahitants() { + return metropolitanAareaInahitants; + } + + public void setMetropolitanAareaInahitants(DoubleValue metropolitanAareaInahitants) { + this.metropolitanAareaInahitants = metropolitanAareaInahitants; + } + + public DoubleValue getCityInhabitants() { + return cityInhabitants; + } + + public void setCityInhabitants(DoubleValue cityInhabitants) { + this.cityInhabitants = cityInhabitants; + } + + public DoubleValue getPopulationDensity() { + return populationDensity; + } + + public void setPopulationDensity(DoubleValue populationDensity) { + this.populationDensity = populationDensity; + } + + public DoubleValue getMales() { + return males; + } + + public void setMales(DoubleValue males) { + this.males = males; + } + + public DoubleValue getFemales() { + return females; + } + + public void setFemales(DoubleValue females) { + this.females = females; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PriceSpecification.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PriceSpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..c6e3db4f06fa5fcd99433a21e0ddf5312c200a62 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/PriceSpecification.java @@ -0,0 +1,79 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PriceSpecification { + @Schema(description = "", required = false) + private List<String> audience; + + @Schema(description = "", required = false) + private Double eligibleQuantity; + + @Schema(description = "", required = false) + private Double maxPrice; + + @Schema(description = "", required = false) + private Double minPrice; + + @Schema(description = "", required = false) + private Double price; + + public List<String> getAudience() { + return audience; + } + + public void setAudience(List<String> audience) { + this.audience = audience; + } + + public Double getEligibleQuantity() { + return eligibleQuantity; + } + + public void setEligibleQuantity(Double eligibleQuantity) { + this.eligibleQuantity = eligibleQuantity; + } + + public Double getMaxPrice() { + return maxPrice; + } + + public void setMaxPrice(Double maxPrice) { + this.maxPrice = maxPrice; + } + + public Double getMinPrice() { + return minPrice; + } + + public void setMinPrice(Double minPrice) { + this.minPrice = minPrice; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Review.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Review.java new file mode 100644 index 0000000000000000000000000000000000000000..9b6404cfc25a5f0df0db43c53dae89f0b2cf0b45 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Review.java @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Review { + @Schema(description = "", required = false) + private String article; + + @Schema(description = "", required = false) + private String origin; + + @Schema(description = "", required = false) + private Integer ratingValue; + + @Schema(description = "", required = false) + private Integer starRating; + + public String getArticle() { + return article; + } + + public void setArticle(String article) { + this.article = article; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public Integer getRatingValue() { + return ratingValue; + } + + public void setRatingValue(Integer ratingValue) { + this.ratingValue = ratingValue; + } + + public Integer getStarRating() { + return starRating; + } + + public void setStarRating(Integer starRating) { + this.starRating = starRating; + } + @Override + public String toString() { + return "{\"article\":\"" + article + "\", \"origin\":\"" + origin + + "\", \"ratingValue\":" + ratingValue + ", \"starRating\":" + starRating+ "}"; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/RouteType.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/RouteType.java new file mode 100644 index 0000000000000000000000000000000000000000..845378aeafb064b8064eea78cd5874b1553903b4 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/RouteType.java @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum RouteType { + bus, + cableCar, + cableTram, + ferry, + funicular, + monorail, + subway, + train, + tram, + trolleybus +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Services.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Services.java new file mode 100644 index 0000000000000000000000000000000000000000..8010673d1e4dabb9b0ae656ad7226e4a3f19de11 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/Services.java @@ -0,0 +1,152 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Services { + + @Schema( required = false) + private Boolean defibrillator; + + @Schema( required = false) + private Boolean emergencyPhone; + + @Schema( required = false) + private Boolean informationBoardDevice; + + @Schema( required = false) + private Boolean interactiveDevice; + + @Schema( required = false) + private Boolean messageDevice; + + @Schema( required = false) + private Boolean purchaseDevice; + + @Schema( required = false) + private Boolean restBench; + + @Schema( required = false) + private Boolean shelters; + + @Schema( required = false) + private Boolean timetableDevice; + + @Schema( required = false) + private Boolean voiceDevice; + + @Schema( required = false) + private Boolean wheelChairAccessible; + + public Boolean getDefibrillator() { + return defibrillator; + } + + public void setDefibrillator(Boolean defibrillator) { + this.defibrillator = defibrillator; + } + + public Boolean getEmergencyPhone() { + return emergencyPhone; + } + + public void setEmergencyPhone(Boolean emergencyPhone) { + this.emergencyPhone = emergencyPhone; + } + + public Boolean getInformationBoardDevice() { + return informationBoardDevice; + } + + public void setInformationBoardDevice(Boolean informationBoardDevice) { + this.informationBoardDevice = informationBoardDevice; + } + + public Boolean getInteractiveDevice() { + return interactiveDevice; + } + + public void setInteractiveDevice(Boolean interactiveDevice) { + this.interactiveDevice = interactiveDevice; + } + + public Boolean getMessageDevice() { + return messageDevice; + } + + public void setMessageDevice(Boolean messageDevice) { + this.messageDevice = messageDevice; + } + + public Boolean getPurchaseDevice() { + return purchaseDevice; + } + + public void setPurchaseDevice(Boolean purchaseDevice) { + this.purchaseDevice = purchaseDevice; + } + + public Boolean getRestBench() { + return restBench; + } + + public void setRestBench(Boolean restBench) { + this.restBench = restBench; + } + + public Boolean getShelters() { + return shelters; + } + + public void setShelters(Boolean shelters) { + this.shelters = shelters; + } + + public Boolean getTimetableDevice() { + return timetableDevice; + } + + public void setTimetableDevice(Boolean timetableDevice) { + this.timetableDevice = timetableDevice; + } + + public Boolean getVoiceDevice() { + return voiceDevice; + } + + public void setVoiceDevice(Boolean voiceDevice) { + this.voiceDevice = voiceDevice; + } + + public Boolean getWheelChairAccessible() { + return wheelChairAccessible; + } + + public void setWheelChairAccessible(Boolean wheelChairAccessible) { + this.wheelChairAccessible = wheelChairAccessible; + } + + @Override + public String toString() { + return "{\"defibrillator\":" + defibrillator + ", \"emergencyPhone\":" + emergencyPhone + + ", \"informationBoardDevice\":" + informationBoardDevice + ", \"interactiveDevice\":" + interactiveDevice + + ", \"messageDevice\":" + messageDevice + ", \"purchaseDevice\":" + purchaseDevice + ", \"restBench\":" + restBench + + ", \"shelters\":" + shelters + ", \"timetableDevice\":" + timetableDevice + + ", \"dateMvoiceDeviceodified\":" + voiceDevice + + ", \"wheelChairAccessible\":" + wheelChairAccessible +"}"; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StarRatingDetail.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StarRatingDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..df9f5d3df2aed42a26cff5b248cf6279a3ff3db7 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StarRatingDetail.java @@ -0,0 +1,131 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class StarRatingDetail { + @Schema(description = "", required = false, example = "") + private Double S1; + + @Schema(description = "", required = false, example = "") + private Double S2; + + @Schema(description = "", required = false, example = "") + private Double S3; + + @Schema(description = "", required = false, example = "") + private Double S4; + + @Schema(description = "", required = false, example = "") + private Double S5; + + @Schema(description = "", required = false, example = "") + private Double S6; + + @Schema(description = "", required = false, example = "") + private Double S7; + + @Schema(description = "", required = false, example = "") + private Double S8; + + @Schema(description = "", required = false, example = "") + private Double S9; + + @Schema(description = "", required = false, example = "") + private Double S10; + + public Double getS1() { + return S1; + } + + public void setS1(Double s1) { + S1 = s1; + } + + public Double getS2() { + return S2; + } + + public void setS2(Double s2) { + S2 = s2; + } + + public Double getS3() { + return S3; + } + + public void setS3(Double s3) { + S3 = s3; + } + + public Double getS4() { + return S4; + } + + public void setS4(Double s4) { + S4 = s4; + } + + public Double getS5() { + return S5; + } + + public void setS5(Double s5) { + S5 = s5; + } + + public Double getS6() { + return S6; + } + + public void setS6(Double s6) { + S6 = s6; + } + + public Double getS7() { + return S7; + } + + public void setS7(Double s7) { + S7 = s7; + } + + public Double getS8() { + return S8; + } + + public void setS8(Double s8) { + S8 = s8; + } + + public Double getS9() { + return S9; + } + + public void setS9(Double s9) { + S9 = s9; + } + + public Double getS10() { + return S10; + } + + public void setS10(Double s10) { + S10 = s10; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnected.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnected.java new file mode 100644 index 0000000000000000000000000000000000000000..875528ba529ed8ea3c1c3029db8e2bcfe3f1db59 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnected.java @@ -0,0 +1,134 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class StationConnected { + + @Schema( required = false) + private String architect; + + @Schema( required = false) + private String commissioningDate; + + @Schema( required = false) + private String constructionDate; + + @Schema( required = false) + private List<Currency> currencyAccepted; + + @Schema( required = false) + private List<String> featuredArtist; + + @Schema( required = false) + private StationConnectedItem items; + + @Schema( required = false) + private List<PaymentAccepted> paymentAccepted; + + @Schema( required = false) + private List<Services> services; + + public String getArchitect() { + return architect; + } + + public void setArchitect(String architect) { + this.architect = architect; + } + + public String getCommissioningDate() { + return commissioningDate; + } + + public void setCommissioningDate(String commissioningDate) { + this.commissioningDate = commissioningDate; + } + + public String getConstructionDate() { + return constructionDate; + } + + public void setConstructionDate(String constructionDate) { + this.constructionDate = constructionDate; + } + + public List<Currency> getCurrencyAccepted() { + return currencyAccepted; + } + + public void setCurrencyAccepted(List<Currency> currencyAccepted) { + this.currencyAccepted = currencyAccepted; + } + + public List<String> getFeaturedArtist() { + return featuredArtist; + } + + public void setFeaturedArtist(List<String> featuredArtist) { + this.featuredArtist = featuredArtist; + } + + public StationConnectedItem getItems() { + return items; + } + + public void setItems(StationConnectedItem items) { + this.items = items; + } + + public List<PaymentAccepted> getPaymentAccepted() { + return paymentAccepted; + } + + public void setPaymentAccepted(List<PaymentAccepted> paymentAccepted) { + this.paymentAccepted = paymentAccepted; + } + + public List<Services> getServices() { + return services; + } + + public void setServices(List<Services> services) { + this.services = services; + } + @Override + public String toString() { + String station ="{\"architect\":\"" + architect +"\",\"commissioningDate\":\""+commissioningDate+"\",\"constructionDate\":\""+constructionDate+ + "\", \"currencyAccepted\":["; + for (Currency ca:currencyAccepted) station=station+"\""+ca+"\","; + station = station.substring(0, station.length()-1); + station = station+"], \"featuredArtist\":["; + for (String fa:featuredArtist) station=station+"\""+fa+"\","; + station = station.substring(0, station.length()-1); + station = station+"], \"items\":"+items.toString()+", \"paymentAccepted\":["; + for (PaymentAccepted pa:paymentAccepted) station=station+"\""+pa+"\","; + station = station.substring(0, station.length()-1); + station = station+"], \"services\":["; + for (Services s:services) station=station+""+s.toString()+","; + station = station.substring(0, station.length()-1); + station = station+"]}"; + + + + return station; + + + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnectedItem.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnectedItem.java new file mode 100644 index 0000000000000000000000000000000000000000..41c2ba866f65ad5cb628b8528bf1c4b9fba5fd32 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationConnectedItem.java @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class StationConnectedItem { + + @Schema( required = false) + private List<String> linesConnected; + + @Schema( required = false) + private StationType stationType; + + public List<String> getLinesConnected() { + return linesConnected; + } + + public void setLinesConnected(List<String> linesConnected) { + this.linesConnected = linesConnected; + } + + public StationType getStationType() { + return stationType; + } + + public void setStationType(StationType stationType) { + this.stationType = stationType; + } + @Override + public String toString() { + String station ="{\"linesConnected\":["; + for (String pt:linesConnected) station=station+"\""+pt+"\","; + station = station.substring(0, linesConnected.size()-1); + station = station+"], \"stationType\":"+stationType+"\"}"; + return station; + + + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationType.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationType.java new file mode 100644 index 0000000000000000000000000000000000000000..ca984a6fe7a3abcc1edd8165ae56310e766e392b --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/StationType.java @@ -0,0 +1,31 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum StationType { + aerialLift, + bike, + bus, + cableTram, + ferry, + funicular, + monorail, + rail, + subway, + train, + tram, + trolleybus +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/SubCategory.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/SubCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..d4a800efd8a40cd9b0a7bc51ad7fb465d972f162 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/SubCategory.java @@ -0,0 +1,190 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum SubCategory { + museum, + art, + archaeology, + contemporaryArt, + modernArt, + appliedArts, + decorativeArts, + scienceAndTechnology, + fineArts, + music, + sacredArt, + specials, + literature, + medicineAndPharmacy, + maritime, + transports, + military, + wax, + popularArtsAndTraditions, + numismatic, + ceramics, + sumptuaryArts, + naturalScience, + prehistoric, + ethnology, + railway, + mining, + textile, + sculpture, + multiDisciplinar, + painting, + paleonthology, + thematic, + architecture, + museumHouse, + universitary, + bullfighting, + excursion, + sea, + mountain, + river, + countryside, + ancientCity, + cultural, + culinary, + wineRoute, + parksAndGardens, + park, + garden, + fountain, + religiousWorship, + church, + cathedral, + synagogue, + mosque, + buddhistTemple, + hinduTemple, + monastery, + sanctuary, + cemetery, + sumptuar, + history, + castle, + warMemorials, + memorial, + fortifiedCastles, + archaeologicalSite, + crypt, + cave, + shopping, + departmentStore, + luxuryStores, + outlet, + mall, + clothing, + mensClothing, + womensClothing, + childrenClothing, + localProducts, + souvenir, + wine, + pastry, + chocolate, + confectionery, + jewelry, + watch, + shoe, + perfume, + cosmetics, + press, + sport, + optics, + leatherGoods, + decoration, + market, + bike, + book, + computer, + convenience, + electronic, + florist, + furniture, + grocery, + home, + liquor, + mobile, + movierental, + pawnShop, + tire, + toy, + gastronomy, + worldCuisine, + traditional, + provencal, + mediterranean, + greek, + spanish, + brazilian, + lebanese, + creole, + mauritian, + reunion, + hawaiian, + mexican, + american, + texMex, + vegetarian, + fish, + seafood, + indian, + vietnamese, + thai, + laosian, + cambodian, + chinese, + moroccan, + tunisian, + african, + sushi, + japanese, + scandinavian, + russian, + outdoorActivities, + rafting, + canyoning, + aquatichiking, + hiking, + viaferrata, + climbing, + kitesurfing, + canoeing, + paddleboarding, + jetSki, + catamaran, + sailing, + surfing, + deltaPlane, + skiing, + scooter, + karting, + wellness, + spa, + haman, + jacuzzi, + hotSpring, + thalasso, + bodyCare, + swimmingPool, + relaxationArea, + massage, + careCenter +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TouristType.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TouristType.java new file mode 100644 index 0000000000000000000000000000000000000000..9a44df70bd6331511dc777678758346ede1435a2 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TouristType.java @@ -0,0 +1,73 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum TouristType { + ADVENTURETOURISM, + ASTRONOMYTOURISM, + BACKPACKINGTOURISM, + BEACHANDSUNTOURISM, + BEERTOURISM, + BIRDINGTOURISM, + BULLFIGHTINGTOURISM, + BUSINESS, + COMMUNITY_BASEDTOURISM, + CRUISETOURISM, + CULTURALTOURISM, + CYCLINGTOURISM, + DIVINGTOURISM, + ECOTOURISM, + EVENTSANDFESTIVALSTOURISM, + FAMILYTOURISM, + FILMTOURISM, + FISHINGTOURISM, + FOODTOURISM, + GAMBLINGTOURISM, + GEOLOGICALTOURISM, + HERITAGETOURISM, + HUNTINGTOURISM, + INDUSTRIALTOURISM, + LANGUAGETOURISM, + LGTBITOURISM, + LUXURYTOURISM, + MEDICALTOURISM, + MEMORIALTOURISM, + MICETOURISM, + NATURETOURISM, + OLIVEOILTOURISM, + PARTYTOURISM, + PHOTOGRAPHYTOURISM, + RELIGIOUSTOURISM, + ROMANTICTOURISM, + RURALTOURISM, + SAFARITOURISM, + SAILINGTOURISM, + SENIORTOURISM, + SHOPPINGTOURISM, + SHORTBREAKTOURISM, + SINGLESTOURISM, + SPORTSTOURISM, + TOURISM, + TREKKINGTOURISM, + URBANTOURISM, + WATERSPORTSTOURISM, + WEDDING_HONEYMOONTOURISM, + WELLNESSTOURISM, + WHISKYTOURISM, + WINETOURISM, + WINTERSPORTSTOURISM, + WOMENTOURISM +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TransportService.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TransportService.java new file mode 100644 index 0000000000000000000000000000000000000000..ff3ad3aef868362db23984609813b76c67f837aa --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TransportService.java @@ -0,0 +1,22 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum TransportService { + taxi, + uber, + vtc +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripSchedule.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripSchedule.java new file mode 100644 index 0000000000000000000000000000000000000000..6f4c4cbe2d983d1e4bf08207fff9cdfb135f5910 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripSchedule.java @@ -0,0 +1,164 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TripSchedule { + @Schema(description = "", required = false, example = "") + private String byDay; + + @Schema(description = "", required = false, example = "") + private Integer byMonth; + + @Schema(description = "", required = false, example = "") + private Integer byMonthDay; + + @Schema(description = "", required = false, example = "") + private Integer byMonthWeek; + + @Schema(description = "", required = false, example = "") + private DayOfWeek dayOfWeek; + + @Schema(description = "", required = false, example = "") + private Double duration; + + @Schema(description = "", required = false, example = "") + private String endDate; + + @Schema(description = "", required = false, example = "") + private String endTime; + + @Schema(description = "", required = false, example = "") + private String exceptDate; + + @Schema(description = "", required = false, example = "") + private Integer repeatCount; + + @Schema(description = "", required = false, example = "") + private String repeatFrequency; + + @Schema(description = "", required = false, example = "") + private String startDate; + + @Schema(description = "", required = false, example = "") + private String startTime; + + public String getByDay() { + return byDay; + } + + public void setByDay(String byDay) { + this.byDay = byDay; + } + + public Integer getByMonth() { + return byMonth; + } + + public void setByMonth(Integer byMonth) { + this.byMonth = byMonth; + } + + public Integer getByMonthDay() { + return byMonthDay; + } + + public void setByMonthDay(Integer byMonthDay) { + this.byMonthDay = byMonthDay; + } + + public Integer getByMonthWeek() { + return byMonthWeek; + } + + public void setByMonthWeek(Integer byMonthWeek) { + this.byMonthWeek = byMonthWeek; + } + + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(DayOfWeek dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + public Double getDuration() { + return duration; + } + + public void setDuration(Double duration) { + this.duration = duration; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public String getExceptDate() { + return exceptDate; + } + + public void setExceptDate(String exceptDate) { + this.exceptDate = exceptDate; + } + + public Integer getRepeatCount() { + return repeatCount; + } + + public void setRepeatCount(Integer repeatCount) { + this.repeatCount = repeatCount; + } + + public String getRepeatFrequency() { + return repeatFrequency; + } + + public void setRepeatFrequency(String repeatFrequency) { + this.repeatFrequency = repeatFrequency; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripStatus.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..12a59ace5bb222f584f78af59466cc6ad5824c05 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Common/TripStatus.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Common; + +public enum TripStatus { + cancelled, + closed, + finished, + opened, + postponed, + rescheduled, + scheduled, + suspended +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/DataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/DataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..888b0876f484b0ddf6812da840e2985647c7fc30 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/DataModel.java @@ -0,0 +1,233 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.Response; +import com.tecnalia.urbanite.storage.controllers.*; +import org.apache.commons.lang3.StringUtils; +import org.codehaus.jettison.json.JSONObject; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public enum DataModel { + + trafficFlowObserved ("trafficFlowObserved", //id + "Traffic Flow Observed", //name + "An observation of traffic flow conditions at a certain place and time.", //description + "https://github.com/smart-data-models/dataModel.Transportation/tree/master/TrafficFlowObserved", //reference + new TrafficFlowObservedController()) //implementation class + , + daySpecification ("daySpecification", + "Day specification", + "Information about a day: working day, school day, public holiday, day of week.", + "", + new DaySpecificationController()) + , + calendar("calendar", + "Calendar", + "Information about calendars: year, city, days...", + "", + new CalendarController()) + , + airQualityObserved("airQualityObserved", + "Air Quality Observed", + "An observation of air quality conditions at a certain place and time.", + "https://github.com/smart-data-models/dataModel.Environment/tree/master/AirQualityObserved", + new AirQualityObservedController()) + , + nosiseLevelObserved("nosiseLevelObserved", + "Noise level Observed", + "An observation of those acoustic parameters that estimate noise pressure levels at a certain place and time.", + "https://github.com/smart-data-models/dataModel.Environment/tree/master/NoiseLevelObserved", + new NoiseLevelObservedController()) + , + electroMagneticObserved("electroMagneticObserved", + "ElectroMagnetic Observed", + "The Data Model is intended to measure excessive electric and magnetic fields (EMFs), or radiation in a work or public environment according to the level of exposure to electromagnetic fields on the air", + "https://github.com/smart-data-models/dataModel.Environment/tree/master/ElectroMagneticObserved", + new ElectroMagneticObservedController()) + , + weatherObserved ("weatherObserved", + "Weather Observed", + "An observation of weather conditions at a certain place and time.", + "https://github.com/smart-data-models/dataModel.Weather/tree/master/WeatherObserved", + new WeatherObservedController()) + , + event ("event", + "Event", + "Upcoming or past event associated with this place, organization, or action.", + "https://github.com/smart-data-models/dataModel.TourismDestinations/tree/master/Event", + new EventController()) + , + censusObserved ("censusObserved", + "Census Observed", + "Census observation.", + "https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/census-ngsi.jsonld", + new CensusObservedController()) + , + populationObserved ("populationObserved", + "populationObserved", + "populationObserved.", + "https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/population-ngsi.jsonld", + new PopulationObservedController()) + , + pointOfInterest ("pointOfInterest", + "Point of Interest", + "This entity contains a harmonised geographic description of a Point of Interest", + "https://github.com/smart-data-models/dataModel.PointOfInterest/blob/master/PointOfInterest", + new PointOfInterestController()) + , + transportStation ("transportStation", + "TransportStation", + "The data model is a general description of urban stations (Metro, Bus, Tram, Heliport, ...) according to the GFTS standard https://developers.google.com/transit/gtfs/reference/#stopstxt, as well the detailed description of these (means of access, platform, assistance, ...).", + "https://github.com/smart-data-models/dataModel.Transportation/tree/master/TransportStation", + new TransportStationController()) + , + gtfsShape ("gtfsShape", + "GtfsShape", + "GtfsShape", + "https://github.com/smart-data-models/dataModel.UrbanMobility/blob/master/GtfsShape", + new GtfsShapeController()) + , + touristTrip ("touristTrip", + "TouristTrip", + "A tourist trip. A created itinerary of visits to one or more places of interest (TouristAttraction/TouristDestination) often linked by a similar theme, geographic area, or interest to a particular touristType. The UNWTO defines tourism trip as the Trip taken by visitors.", + "https://github.com/smart-data-models/dataModel.TourismDestinations/blob/master/TouristTrip", + new TouristTripController()) + , + + originDestinationMatrix ("originDestinationMatrix", + "Origin Destination Matrix", + "Origin Destination Matrix.", + "https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/ODMatrix-ngsi.jsonld", + new OriginDestinationMatrixController()) + , + + mapLayer ("mapLayer", + "GeoJson Storage", + "GeoJson Storage", + "https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/maplayer-ngsi.jsonld", + new MapLayerController()) + , + vehicle ("vehicle", + "Vehicle", + "This entity models a particular vehicle model, including all properties which are common to multiple vehicle instances belonging to such model.", + "https://github.com/smart-data-models/dataModel.UrbanMobility/blob/master/GtfsShape", + new VehicleController()) + , + /* + * Rest of models here + */ + ; + + private String id; + private String name; + private String description; + private String reference; + private IGenericController iOps; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getReference() { + return reference; + } + public void setReference(String reference) { + this.reference = reference; + } + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + private DataModel(String id, String name, String description, String reference, IGenericController iOps) { + this.iOps = iOps; + this.id = id; + this.name = name; + this.description = description; + this.reference = reference; + } + + public APIResponse insertData(City city, String data) { + return iOps.insertData(city, data); + } + + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + return iOps.getTDataRange(city, startDate, endDate, filters, returnFields, limit, sort); + } + + public APIResponse updateData(City city, String id, String data) { + return iOps.updateData(city, id, data); + } + + public APIResponse getDataByID(City city, String id) { + return iOps.getDataByID(city, id); + } + + public APIResponse getTData (City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + return iOps.getTData (city, filters, returnFields, limit, sort); + } + public JSONObject getExample() { + return iOps.getExample(); + } + public APIResponse getDistinct(City city, String field) { + return iOps.getDistinct(city, field); + } + public APIResponse getDistinct(City city, String[] field) { + return iOps.getDistinct(city, field); + } + public APIResponse deleteDataByID(City city, String id) { + return iOps.deleteDataByID(city, id); + } + + public Response aggregate (City city, String metric, Date startDate, Date endDate, AggregatorEnum aggregatorEnum, String downsample, Map<String,String> tags) + { + String compoundMetric = (this.getId() + "." + metric).toLowerCase(); + return new AggregatorController().aggregate(city, compoundMetric, startDate, endDate, aggregatorEnum, downsample, tags); + } + + public static Boolean any(String id) + { + if(!StringUtils.isBlank(id)) + { + for(DataModel dataModel : DataModel.values()) + { + if(id.equals(dataModel.id)) + { + return true; + } + } + } + return false; + } + + + +} + diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/AirQualityObserved.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/AirQualityObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..276202d29ff5be6426c7b0745bf859b8c7c297d0 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/AirQualityObserved.java @@ -0,0 +1,654 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.TypeOfLocation; +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class AirQualityObserved { + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "Air quality index is a number used to report the quality of the air on any given day.", required = false) + private Integer airQualityIndex; + + @Schema(description = "Overall qualitative level of health concern corresponding to the air quality observed.", required = false) + private String airQualityLevel; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "Arsenic detected.", required = false) + private Double as; + + @Schema(description = "Benzene detected.", required = false) + private Double c6h6; + + @Schema(description = "Cadmium detected.", required = false) + private Double cd; + + @Schema(description = "Carbon Monoxide detected.", required = false) + private Double co; + + @Schema(description = "Carbon Dioxide detected.", required = false) + private Double co2; + + @Schema(description = "Qualitative Carbon Monoxide presence.", required = false) + private String coLevel; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTCformat.", required = true) + private String dateObserved; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "Nickel detected.", required = false) + private Double ni; + + @Schema(description = "Nitrogen monoxide detected.", required = false) + private Double no; + + @Schema(description = "Nitrogen dioxide detected.", required = false) + private Double no2; + + @Schema(description = "Other Nitrogen oxides detected.", required = false) + private Double nox; + + @Schema(description = "Ozone detected.", required = false) + private Double o3; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "Lead detected.", required = false) + private Double pb; + + @Schema(description = "Particulate matter 10 micrometers or less in diameter.", required = false) + private Double pm10; + + @Schema(description = "Particulate matter 2.5 micrometers or less in diameter.", required = false) + private Double pm25; + + @Schema(description = "Amount of water rain.", required = false) + private Double precipitation; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "A reference to a point of interest (usually an air quality station) associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "A reference to the weather observed associated to the air quality conditions described by this entity.", required = false) + private String refWeatherObserved; + + @Schema(description = "Humidity in the Air.", required = false) + private Double relativeHumidity; + + @Schema(description = "Reliability (percentage, expressed in parts per one) corresponding to the air quality observed.", required = false) + private Double reliability; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "Hydrogen sulfide detected.", required = false) + private Double sh2; + + @Schema(description = "Sulfur dioxide detected.", required = false) + private Double so2; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "'Temperature of the item.", required = false) + private Double temperature; + + @Schema(description = "NGSI Entity type. Must be 'AirQualityObserved'.", required = true) + private String type; + + @Schema(description = "Type of location of the sampled item.", required = false) + private TypeOfLocation typeofLocation; + + @Schema(description = "Alkanes <C10, ketones <C6, aldehydes <C10, carboxylic acids <C5, aspirits<C7, Alkenes <C8, Aromatics.", required = false) + private Double volatileOrganicCompoundsTotal; + + @Schema(description = "Direction of the weather vane.", required = false) + private Double windDirection; + + @Schema(description = "Intensity of the wind.", required = false) + private Double windSpeed; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public Integer getAirQualityIndex() { + return airQualityIndex; + } + + public void setAirQualityIndex(Integer airQualityIndex) { + this.airQualityIndex = airQualityIndex; + } + + public String getAirQualityLevel() { + return airQualityLevel; + } + + public void setAirQualityLevel(String airQualityLevel) { + this.airQualityLevel = airQualityLevel; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public Double getAs() { + return as; + } + + public void setAs(Double as) { + this.as = as; + } + + public Double getC6h6() { + return c6h6; + } + + public void setC6h6(Double c6h6) { + this.c6h6 = c6h6; + } + + public Double getCd() { + return cd; + } + + public void setCd(Double cd) { + this.cd = cd; + } + + public Double getCo() { + return co; + } + + public void setCo(Double co) { + this.co = co; + } + + public Double getCo2() { + return co2; + } + + public void setCo2(Double co2) { + this.co2 = co2; + } + + public String getCoLevel() { + return coLevel; + } + + public void setCoLevel(String coLevel) { + this.coLevel = coLevel; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getNi() { + return ni; + } + + public void setNi(Double ni) { + this.ni = ni; + } + + public Double getNo() { + return no; + } + + public void setNo(Double no) { + this.no = no; + } + + public Double getNo2() { + return no2; + } + + public void setNo2(Double no2) { + this.no2 = no2; + } + + public Double getNox() { + return nox; + } + + public void setNox(Double nox) { + this.nox = nox; + } + + public Double getO3() { + return o3; + } + + public void setO3(Double o3) { + this.o3 = o3; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public Double getPb() { + return pb; + } + + public void setPb(Double pb) { + this.pb = pb; + } + + public Double getPm10() { + return pm10; + } + + public void setPm10(Double pm10) { + this.pm10 = pm10; + } + + public Double getPm25() { + return pm25; + } + + public void setPm25(Double pm25) { + this.pm25 = pm25; + } + + public Double getPrecipitation() { + return precipitation; + } + + public void setPrecipitation(Double precipitation) { + this.precipitation = precipitation; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public String getRefWeatherObserved() { + return refWeatherObserved; + } + + public void setRefWeatherObserved(String refWeatherObserved) { + this.refWeatherObserved = refWeatherObserved; + } + + public Double getRelativeHumidity() { + return relativeHumidity; + } + + public void setRelativeHumidity(Double relativeHumidity) { + this.relativeHumidity = relativeHumidity; + } + + public Double getReliability() { + return reliability; + } + + public void setReliability(Double reliability) { + this.reliability = reliability; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public Double getSh2() { + return sh2; + } + + public void setSh2(Double sh2) { + this.sh2 = sh2; + } + + public Double getSo2() { + return so2; + } + + public void setSo2(Double so2) { + this.so2 = so2; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public TypeOfLocation getTypeofLocation() { + return typeofLocation; + } + + public void setTypeofLocation(TypeOfLocation typeofLocation) { + this.typeofLocation = typeofLocation; + } + + public Double getVolatileOrganicCompoundsTotal() { + return volatileOrganicCompoundsTotal; + } + + public void setVolatileOrganicCompoundsTotal(Double volatileOrganicCompoundsTotal) { + this.volatileOrganicCompoundsTotal = volatileOrganicCompoundsTotal; + } + + public Double getWindDirection() { + return windDirection; + } + + public void setWindDirection(Double windDirection) { + this.windDirection = windDirection; + } + + public Double getWindSpeed() { + return windSpeed; + } + + public void setWindSpeed(Double windSpeed) { + this.windSpeed = windSpeed; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved, location + if (id == null || type == null || dateObserved == null || location == null) return false; + + //DateObserved: valid date + if (Utils.ISO2Date(dateObserved) == null) return false; + + //type: must be "AirQualityObserved"; + if (type.compareTo("AirQualityObserved") != 0) return false; + + //Minimum/maximum values + if (airQualityIndex != null && airQualityIndex < 0) return false; + if (as != null && as < 0) return false; + if (c6h6 != null && c6h6 < 0) return false; + if (cd != null && cd < 0) return false; + if (co != null && co < 0) return false; + if (co2 != null && co2 < 0) return false; + if (ni != null && ni < 0) return false; + if (no != null && no < 0) return false; + if (no2 != null && no2 < 0) return false; + if (nox != null && nox < 0) return false; + if (o3 != null && o3 < 0) return false; + if (pb != null && pb < 0) return false; + if (pm10 != null && pm10 < 0) return false; + if (pm25 != null && pm25 < 0) return false; + if (precipitation != null && precipitation < 0) return false; + if (relativeHumidity!= null && (relativeHumidity < 0 || relativeHumidity > 1)) return false; + if (reliability!= null && (reliability < 0 || reliability > 1)) return false; + if (sh2 != null && sh2 < 0) return false; + if (so2 != null && so2 < 0) return false; + if (volatileOrganicCompoundsTotal != null && volatileOrganicCompoundsTotal < 0) return false; + if (windDirection!= null && (windDirection < -180 || windDirection > 180)) return false; + if (windSpeed != null && windSpeed < 0) return false; + if (airQualityLevel != null && airQualityLevel.length() < 2) return false; + + //all ok + return true; + } + + public static AirQualityObserved createAirQualityObserved(String data) { + + AirQualityObserved aq = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + aq = Utils.JSON2Object(obj.toString(), AirQualityObserved.class); + aq.setLocation(pol); + aq.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to AirQualityObserved: " + e.getMessage()); + } + + return aq; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"airQualityIndex\":\"" + airQualityIndex + + "\", \"airQualityLevel\":\"" + airQualityLevel + "\", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\", \"as\":\"" + as + "\", \"c6h6\":\"" + c6h6 + + "\", \"cd\":\"" + cd + "\", \"co\":\"" + co + "\", \"co2\":\"" + co2 + "\", \"coLevel\":\"" + coLevel + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"ni\":\"" + ni + "\", \"no\":\"" + no + "\", \"no2\":\"" + no2 + + "\", \"nox\":\"" + nox + "\", \"o3\":\"" + o3 + "\", \"owner\":\"" + owner + "\", \"pb\":\"" + pb + + "\", \"pm10\":\"" + pm10 + "\", \"pm25\":\"" + pm25 + "\", \"precipitation\":\"" + precipitation + + "\", \"refDevice\":\"" + refDevice + "\", \"refPointOfInterest\":\"" + refPointOfInterest + + "\", \"refWeatherObserved\":\"" + refWeatherObserved + "\", \"relativeHumidity\":\"" + + relativeHumidity + "\", \"reliability\":\"" + reliability + "\", \"seeAlso\":\"" + seeAlso + + "\", \"sh2\":\"" + sh2 + "\", \"so2\":\"" + so2 + "\", \"source\":\"" + source + + "\", \"temperature\":\"" + temperature + "\", \"type\":\"" + type + "\", \"typeofLocation\":\"" + + typeofLocation + "\", \"volatileOrganicCompoundsTotal\":\"" + volatileOrganicCompoundsTotal + + "\", \"windDirection\":\"" + windDirection + "\", \"windSpeed\":\"" + windSpeed + "\", \"context\":\"" + + context + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/ElectroMagneticObserved.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/ElectroMagneticObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..00fa6b66b2e7254d2f1d924560c03ee134f122dc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/ElectroMagneticObserved.java @@ -0,0 +1,373 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ElectroMagneticObserved { + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTCformat.", required = true) + private String dateObserved; + + @Schema(description = "Observation period start date and time.", required = false) + private String dateObservedFrom; + + @Schema(description = "Observation period end date and time.", required = false) + private String dateObservedTo; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Level corresponding to the observed survey. The unit code (text) is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **MHz** represents Mega Hertz.", required = true) + private String EMF; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "A reference to a point of interest (usually an air quality station) associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "Percent for confidence Factor. The unit code (text) is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **P1** represents Percent. ", required = false) + private Double reliability; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'ElectroMagneticObserved'.", required = true) + private String type; + + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateObservedFrom() { + return dateObservedFrom; + } + + public void setDateObservedFrom(String dateObservedFrom) { + this.dateObservedFrom = dateObservedFrom; + } + + public String getDateObservedTo() { + return dateObservedTo; + } + + public void setDateObservedTo(String dateObservedTo) { + this.dateObservedTo = dateObservedTo; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getEMF() { + return EMF; + } + + public void setEMF(String eMF) { + EMF = eMF; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public Double getReliability() { + return reliability; + } + + public void setReliability(Double reliability) { + this.reliability = reliability; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + + if (id == null || type == null || dateObserved == null || location == null) return false; + + if (Utils.ISO2Date(dateObserved) == null) return false; + + if (type.compareTo("ElectroMagneticObserved") != 0) return false; + + //Minimum/maximum values + + + //all ok + return true; + } + + public static ElectroMagneticObserved createElectroMagneticObserved(String data) { + + ElectroMagneticObserved aq = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + aq = Utils.JSON2Object(obj.toString(), ElectroMagneticObserved.class); + aq.setLocation(pol); + aq.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to ElectroMagneticObserved: " + e.getMessage()); + } + + return aq; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\"" + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\"" + + "\", \"EMF\":\"" + EMF + "\", \"reliability\":\"" + reliability + + "\", \"refDevice\":\"" + refDevice + "\", \"refPointOfInterest\":\"" + refPointOfInterest + + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + + "\", \"type\":\"" + type + "\", \"typeofLocation\":\"" + + "\", \"context\":\"" + + context + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/EnvironmentDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/EnvironmentDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..0c08267aac340ab4d4daea3ddbd24fa6dd33c748 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/EnvironmentDataModel.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +public enum EnvironmentDataModel { + AIRQUALITYOBSERVED, + NOISELEVELOBSERVED, + ELECTROMAGNETICOBSERVED + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/NoiseLevelObserved.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/NoiseLevelObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..c5bc448077993760e44ef20a2dac7e8b55314408 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Environment/NoiseLevelObserved.java @@ -0,0 +1,435 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Environment; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.TypeOfLocation; +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class NoiseLevelObserved { + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTCformat.", required = false) + private String dateObserved; + + @Schema(description = "Observation period start date and time.", required = true) + private String dateObservedFrom; + + @Schema(description = "Observation period end date and time.", required = true) + private String dateObservedTo; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "A reference to a point of interest (usually an air quality station) associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "A reference to the weather observed associated to the air quality conditions described by this entity.", required = false) + private String refWeatherObserved; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "Class of sonometer (0, 1, 2) according to ANSI used for taking this observation.", required = false) + private String sonometerClass; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'NoiseLevelObserved'.", required = true) + private String type; + + @Schema(description = "frequencies", required = false) + private HashMap<String, Double> frequencies; + + @Schema(description = "LAmax", required = false) + private Double LAmax; + + @Schema(description = "LAeq", required = false) + private Double LAeq; + + @Schema(description = "LAeq_d", required = false) + private Double LAeq_d; + + @Schema(description = "LAS'.", required = false) + private Double LAS; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateObservedFrom() { + return dateObservedFrom; + } + + public void setDateObservedFrom(String dateObservedFrom) { + this.dateObservedFrom = dateObservedFrom; + } + + public String getDateObservedTo() { + return dateObservedTo; + } + + public void setDateObservedTo(String dateObservedTo) { + this.dateObservedTo = dateObservedTo; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public String getRefWeatherObserved() { + return refWeatherObserved; + } + + public void setRefWeatherObserved(String refWeatherObserved) { + this.refWeatherObserved = refWeatherObserved; + } + + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSonometerClass() { + return sonometerClass; + } + + public void setSonometerClass(String sonometerClass) { + this.sonometerClass = sonometerClass; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + public Double getLAmax() { + return LAmax; + } + + public void setLAmax(Double lAmax) { + LAmax = lAmax; + } + + public Double getLAeq() { + return LAeq; + } + + public void setLAeq(Double lAeq) { + LAeq = lAeq; + } + + public Double getLAeq_d() { + return LAeq_d; + } + + public void setLAeq_d(Double lAeq_d) { + LAeq_d = lAeq_d; + } + + public Double getLAS() { + return LAS; + } + + public void setLAS(Double lAS) { + LAS = lAS; + } + + public HashMap<String, Double> getFrequencies() { + return frequencies; + } + + public void setFrequencies(HashMap<String, Double> frequencies) { + this.frequencies = frequencies; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved, location + if (id == null || type == null || dateObservedFrom == null || dateObservedTo == null || location == null) return false; + + //DateObserved: valid date + //if (Utils.ISO2Date(dateObserved) == null) return false; + if (Utils.ISO2Date(dateObservedFrom) == null) return false; + if (Utils.ISO2Date(dateObservedTo) == null) return false; + + + //type: must be "NoiseLevelObserved"; + if (type.compareTo("NoiseLevelObserved") != 0) return false; + + //Minimum/maximum values + + + //all ok + return true; + } + + public static NoiseLevelObserved createNoiseLevelObserved(String data) { + + NoiseLevelObserved aq = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + aq = Utils.JSON2Object(obj.toString(), NoiseLevelObserved.class); + aq.setLocation(pol); + aq.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to AirQualityObserved: " + e.getMessage()); + } + + return aq; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\"" + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\"" + + "\", \"refDevice\":\"" + refDevice + "\", \"refPointOfInterest\":\"" + refPointOfInterest + + "\", \"refWeatherObserved\":\"" + refWeatherObserved + "\", \"relativeHumidity\":\"" + + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + + "\", \"type\":\"" + type + "\", \"typeofLocation\":\"" + + "\", \"context\":\"" + + context + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/Event.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/Event.java new file mode 100644 index 0000000000000000000000000000000000000000..407ce8e40374ab43155a820d88ed4050cd2966ca --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/Event.java @@ -0,0 +1,329 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Event; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Event { + + @Schema(description = "The category of the event.", required = false) + private String category; + + @Schema(description = "The championship of the match.", required = false) + private String championship; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The end date and time of the event (in ISO 8601 date format).", required = false) + private String endDate; + + @Schema(description = "The guest team of the match.", required = false) + private String guestTeam; + + @Schema(description = "The home team of the match.", required = false) + private String homeTeam; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the event. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = false) + private LocationGeojson location; + + @Schema(description = "The start date and time of the event (in ISO 8601 date format).", required = false) + private String startDate; + + @Schema(description = "NGSI Entity type. Must be 'Event'.", required = true) + private String type; + + @Schema(description = "Number of passangers of the ship.", required = false) + private int passengerCount; + + @Schema(description = "Harbour of departure of the ship.", required = false) + private String departsFromHarbour; + + @Schema(description = "Harbour of arrival of the ship.", required = false) + private String arrivesToHarbour; + + @Schema(description = "Ship identifier or name.", required = false) + private String ship; + + @Schema(description = "Terminal of the trip of the ship.", required = false) + private String terminal; + + @Schema(description = "Type of the route.", required = false) + private String routeType; + + @Schema(description = "Sub-category of the category attribute.", required = false) + private String subCategory; + + @Schema(hidden = true) + private List<String> context; + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getChampionship() { + return championship; + } + + public void setChampionship(String championship) { + this.championship = championship; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getGuestTeam() { + return guestTeam; + } + + public void setGuestTeam(String guestTeam) { + this.guestTeam = guestTeam; + } + + public String getHomeTeam() { + return homeTeam; + } + + public void setHomeTeam(String homeTeam) { + this.homeTeam = homeTeam; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public int getPassengerCount() { + return passengerCount; + } + + public void setPassengerCount(int passengerCount) { + this.passengerCount = passengerCount; + } + + public String getDepartsFromHarbour() { + return departsFromHarbour; + } + + public void setDepartsFromHarbour(String departsFromHarbour) { + this.departsFromHarbour = departsFromHarbour; + } + + public String getArrivesToHarbour() { + return arrivesToHarbour; + } + + public void setArrivesToHarbour(String arrivesToHarbour) { + this.arrivesToHarbour = arrivesToHarbour; + } + + public String getShip() { + return ship; + } + + public void setShip(String ship) { + this.ship = ship; + } + + public String getTerminal() { + return terminal; + } + + public void setTerminal(String terminal) { + this.terminal = terminal; + } + + public String getRouteType() { + return routeType; + } + + public void setRouteType(String routeType) { + this.routeType = routeType; + } + + public String getSubCategory() { + return subCategory; + } + + public void setSubCategory(String subCategory) { + this.subCategory = subCategory; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type + if (id == null || type == null) return false; + + //startDate and endDate: valid dates (if present) + if (startDate != null && Utils.ISO2Date(startDate) == null) return false; + if (endDate != null && Utils.ISO2Date(endDate) == null) return false; + + //type: must be "Event"; + if (type.compareTo("Event") != 0) return false; + + //all ok + return true; + } + + public static Event createEvent(String data) { + + Event ev = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + ev = Utils.JSON2Object(obj.toString(), Event.class); + ev.setLocation(pol); + ev.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to Event: " + e.getMessage()); + } + + return ev; + } + + @Override + public String toString() { + return "{\"category\":\"" + category + "\", \"championship\":\"" + championship + "\", \"dateCreated\":\"" + + dateCreated + "\", \"dateModified\":\"" + dateModified + "\", \"endDate\":\"" + endDate + + "\", \"guestTeam\":\"" + guestTeam + "\", \"homeTeam\":\"" + homeTeam + "\", \"id\":\"" + id + + "\", \"location\":\"" + location + "\", \"startDate\":\"" + startDate + "\", \"type\":\"" + type + + "\", \"context\":\"" + context + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/EventDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/EventDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..1e5d185a22dbe3227d46b466d05f25fa3c343ae6 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Event/EventDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Event; + +public enum EventDataModel { + EVENT +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GeometryType.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GeometryType.java new file mode 100644 index 0000000000000000000000000000000000000000..6dc8978aae9a98db106bacfce9c2913b5358aff5 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GeometryType.java @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum GeometryType { + Point, + MultiPoint, + LineString, + MultiLineString, + Polygon, + MultiPolygon +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShape.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShape.java new file mode 100644 index 0000000000000000000000000000000000000000..45620ed34137eb698cd84598051f9c7c3905f8b2 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShape.java @@ -0,0 +1,282 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.GtfsShape; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class GtfsShape { + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTC format. ", required = false) + private String dateObserved; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "The geographical shape associated to this entity encoded as GeoJSON LineString or MultiLineString.", required = true) + private LocationGeojson location; + + @Schema(description = "An array of the distance travelled when reaching each of the points that make the LineString or MultiLineString that represents this shape. It shall match the same number of elements as the corresponding LineString or MultiLineString.", required = false) + private List<Integer> distanceTravelled; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'GtfsShape'.", required = true) + private String type; + + @Schema(hidden = true) + private List<String> context; + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public List<Integer> getDistanceTravelled() { + return distanceTravelled; + } + + public void setDistanceTravelled(List<Integer> distanceTravelled) { + this.distanceTravelled = distanceTravelled; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id , type and location + if (id == null || type == null || location == null ) return false; + + if (dateObserved != null) { + if (Utils.ISO2Date(dateObserved) == null) + return false; + } + //type: must be "GtfsShape"; + if (type.compareTo("GtfsShape") != 0) return false; + + + return true; + } + public static GtfsShape createGtfsShape(String data) { + + GtfsShape gshape = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + gshape = Utils.JSON2Object(obj.toString(), GtfsShape.class); + gshape.setLocation(pol); + gshape.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to GtfsShape: " + e.getMessage()); + } + + return gshape; + } + @Override + public String toString() { + return "{\"alternateName\":\"" + alternateName + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"distanceTravelled\":\"" + distanceTravelled + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"owner\":\"" + owner + + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + "\", \"type\":\"" + type + "\", \"context\":\"" + + context + "\"}"; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShapedataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShapedataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..f84c53e37c4598d009fc1a5a034f01a3f16c8c14 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/GtfsShape/GtfsShapedataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.GtfsShape; + +public enum GtfsShapedataModel { + GTFSSHAPE +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayer.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayer.java new file mode 100644 index 0000000000000000000000000000000000000000..012ce3bdc5aa2f2ee0a1d1ecb6ec70ca153e8834 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayer.java @@ -0,0 +1,193 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Map; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + + +import io.swagger.v3.oas.annotations.media.Schema; + +public class MapLayer { + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "Indicates the specific type of the map for visualization mechanism ", required = false) + private String subtype; + + @Schema(description = "NGSI Entity type. Must be 'MapLayer'.", required = true) + private String type; + + @Schema(description = "GeoJson content", required = true) + private JSONObject map ; + + + @Schema(hidden = true) + private List<String> context; + + + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public String getSubtype() { + return subtype; + } + + public void setSubtype(String subtype) { + this.subtype = subtype; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public JSONObject getMap() { + return map; + } + + public void setMap(JSONObject map) { + this.map = map; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + @Schema(hidden = true) + public boolean isValid() { + + + if (id == null || type == null || getMap() == null ) return false; + + + if (type.compareTo("MapLayer") != 0) return false; + + + return true; + } + + public static MapLayer createMapLayer(String data) { + + MapLayer aq = new MapLayer();; + + try { + List<String> cnt = null; + JSONObject map = null; + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + if (obj.has("map")) { + map = obj.getJSONObject("map"); + + } + if (obj.has("type")) { + aq.setType(obj.getString("type")); + } + if (obj.has("name")) { + aq.setName(obj.getString("name")); + } + if (obj.has("subtype")) { + aq.setSubtype(obj.getString("subtype")); + } + if (obj.has("alternateName")) { + aq.setAlternateName(obj.getString("alternateName")); + } + if (obj.has("description")) { + aq.setDescription(obj.getString("description")); + } + if (obj.has("id")) { + aq.setId(obj.getString("id")); + } + aq.setMap( map); + //aq = Utils.JSON2Object(obj.toString(), MapLayer.class); + aq.setContext(cnt); + + } catch (Exception e) { + + System.out.println(" Exception converting JSON [" + data + "] to MapLayer: " + e.getMessage()); + return null; + } + + return aq; + } + + @Override + public String toString() { + return "{}"; + } + + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayerDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayerDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..22a5fc97cf0e2bea0f57b52b4755c49b22601006 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Map/MapLayerDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Map; + +public enum MapLayerDataModel { + MAPLAYER +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterest.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterest.java new file mode 100644 index 0000000000000000000000000000000000000000..09e5454a372cb7e2e5e8d41e81854ca48ef13ab2 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterest.java @@ -0,0 +1,365 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.PointOfInterest; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.ContactPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PointOfInterest { + + @Schema(description = "URL from which additional information of the subject can be obtained", required = false) + private String additionalInfoURL; + + @Schema(description = "The mailing address", required = false) + private Address address; + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided", required = false) + private String areaServed; + + @Schema(description = "Category of this point of interest. Allowed values: Those defined by the [Factual taxonomy](https://github.com/Factual/places/blob/master/categories/factual_taxonomy.json) together with the extended categories described by the specification. For instance the value `113` corresponds to beaches, and the value `311` corresponds to museums.", required = false) + private String category; + + @Schema(description = "The details to contact with the item.", required = false) + private ContactPoint contactPoint; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "The name of this item.", required = false) + private List<String> refSeeAlso; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. Must be 'PointOfInterest'.", required = true) + private String type; + + @Schema(description = "Ward ID of the entity corresponding to this observation.", required = false) + private String wardId; + + @Schema(description = "Zone ID of the entity corresponding to this observation.", required = false) + private String zoneId; + + @Schema(description = "Zone name of the entity corresponding to this observation.", required = false) + private String zoneName; + + @Schema(hidden = true) + private List<String> context; + + public String getAdditionalInfoURL() { + return additionalInfoURL; + } + + public void setAdditionalInfoURL(String additionalInfoURL) { + this.additionalInfoURL = additionalInfoURL; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public ContactPoint getContactPoint() { + return contactPoint; + } + + public void setContactPoint(ContactPoint contactPoint) { + this.contactPoint = contactPoint; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public List<String> getRefSeeAlso() { + return refSeeAlso; + } + + public void setRefSeeAlso(List<String> refSeeAlso) { + this.refSeeAlso = refSeeAlso; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getWardId() { + return wardId; + } + + public void setWardId(String wardId) { + this.wardId = wardId; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getZoneName() { + return zoneName; + } + + public void setZoneName(String zoneName) { + this.zoneName = zoneName; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id and type + if (id == null || type == null || name == null || category == null) return false; + + + //type: must be "TrafficFlowObserved"; + if (type.compareTo("PointOfInterest") != 0) return false; + + + return true; + } + public static PointOfInterest createPointOfInterest(String data) { + + PointOfInterest poi = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + poi = Utils.JSON2Object(obj.toString(), PointOfInterest.class); + poi.setLocation(pol); + poi.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to PointOfInterest: " + e.getMessage()); + } + + return poi; + } + @Override + public String toString() { + return "{\"additionalInfoURL\":\"" + additionalInfoURL + "\", \"address\":\"" + address + + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + areaServed + + "\", \"category\":\"" + category + "\", \"contactPoint\":\"" + contactPoint.toString() + "\", \"dataProvider\":\"" + dataProvider + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"owner\":\"" + owner + + "\", \"refSeeAlso\":\"" + refSeeAlso + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + "\", \"type\":\"" + type + "\", \"wardId\":\"" + + wardId + "\", \"zoneId\":\"" + zoneId + + "\", \"zoneName\":\"" + zoneName + "\"context\":\"" + + context + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterestDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterestDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..8a9ba6790a3084f968cb816f3fd62ae50fd01b7f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/PointOfInterest/PointOfInterestDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.PointOfInterest; + +public enum PointOfInterestDataModel { + POINTOFINTEREST +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/CensusObserved.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/CensusObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..91d9d8b4ab669dcb5f510b0c1c85b2dd4b8644cc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/CensusObserved.java @@ -0,0 +1,415 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Population; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class CensusObserved { + //Description: Census Observed + //REF: https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/census-ngsi.jsonld + + @Schema(description = "addressRegion.", required = false) + private String addressRegion; + + @Schema(description = "age.", required = false) + private Integer age; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String createdAt; + + @Schema(description = "currentEconomicStatus.", required = false) + private Integer currentEconomicStatus; + + @Schema(description = "The date and time of this observation in ISO8601 UTC format.", required = true) + private String dateObserved; + + @Schema(description = "disabilityBenefits.", required = false) + private Double disabilityBenefits; + + @Schema(description = "educationAllowances.", required = false) + private Double educationAllowances; + + @Schema(description = "employeeCashIncome.", required = false) + private Double employeeCashIncome; + + @Schema(description = "gender.", required = false) + private String gender; + + @Schema(description = "householdCSWeight.", required = false) + private Double householdCSWeight; + + @Schema(description = "householdID.", required = false) + private Integer householdID; + + @Schema(description = "hsize.", required = false) + private Integer hsize; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "GeoJSON Geometry.", required = false) + private LocationGeojson location; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String modifiedAt; + + @Schema(description = "nationality.", required = false) + private String nationality; + + @Schema(description = "netIncome.", required = false) + private Double netIncome; + + @Schema(description = "oldAgeBenefits.", required = false) + private Double oldAgeBenefits; + + @Schema(description = "personalCSWeight.", required = false) + private Double personalCSWeight; + + @Schema(description = "selfEmploymentLosses.", required = false) + private Double selfEmploymentLosses; + + @Schema(description = "sicknessBenefits.", required = false) + private Double sicknessBenefits; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "survivorBenefits.", required = false) + private Double survivorBenefits; + + @Schema(description = "NGSI Entity type. It has to be 'CensusFlowObserved'.", required = true) + private String type; + + @Schema(description = "unemploymentBenefits.", required = false) + private Double unemploymentBenefits; + + @Schema(hidden = true) + private List<String> context; + + public String getAddressRegion() { + return addressRegion; + } + + public void setAddressRegion(String addressRegion) { + this.addressRegion = addressRegion; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public Integer getCurrentEconomicStatus() { + return currentEconomicStatus; + } + + public void setCurrentEconomicStatus(Integer currentEconomicStatus) { + this.currentEconomicStatus = currentEconomicStatus; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public Double getDisabilityBenefits() { + return disabilityBenefits; + } + + public void setDisabilityBenefits(Double disabilityBenefits) { + this.disabilityBenefits = disabilityBenefits; + } + + public Double getEducationAllowances() { + return educationAllowances; + } + + public void setEducationAllowances(Double educationAllowances) { + this.educationAllowances = educationAllowances; + } + + public Double getEmployeeCashIncome() { + return employeeCashIncome; + } + + public void setEmployeeCashIncome(Double employeeCashIncome) { + this.employeeCashIncome = employeeCashIncome; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public Double getHouseholdCSWeight() { + return householdCSWeight; + } + + public void setHouseholdCSWeight(Double householdCSWeight) { + this.householdCSWeight = householdCSWeight; + } + + public Integer getHouseholdID() { + return householdID; + } + + public void setHouseholdID(Integer householdID) { + this.householdID = householdID; + } + + public Integer getHsize() { + return hsize; + } + + public void setHsize(Integer hsize) { + this.hsize = hsize; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getModifiedAt() { + return modifiedAt; + } + + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + + public String getNationality() { + return nationality; + } + + public void setNationality(String nationality) { + this.nationality = nationality; + } + + public Double getNetIncome() { + return netIncome; + } + + public void setNetIncome(Double netIncome) { + this.netIncome = netIncome; + } + + public Double getOldAgeBenefits() { + return oldAgeBenefits; + } + + public void setOldAgeBenefits(Double oldAgeBenefits) { + this.oldAgeBenefits = oldAgeBenefits; + } + + public Double getPersonalCSWeight() { + return personalCSWeight; + } + + public void setPersonalCSWeight(Double personalCSWeight) { + this.personalCSWeight = personalCSWeight; + } + + public Double getSelfEmploymentLosses() { + return selfEmploymentLosses; + } + + public void setSelfEmploymentLosses(Double selfEmploymentLosses) { + this.selfEmploymentLosses = selfEmploymentLosses; + } + + public Double getSicknessBenefits() { + return sicknessBenefits; + } + + public void setSicknessBenefits(Double sicknessBenefits) { + this.sicknessBenefits = sicknessBenefits; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Double getSurvivorBenefits() { + return survivorBenefits; + } + + public void setSurvivorBenefits(Double survivorBenefits) { + this.survivorBenefits = survivorBenefits; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Double getUnemploymentBenefits() { + return unemploymentBenefits; + } + + public void setUnemploymentBenefits(Double unemploymentBenefits) { + this.unemploymentBenefits = unemploymentBenefits; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved + if (id == null || type == null || dateObserved == null) return false; + + //DateObserved: valid date + if (Utils.ISO2Date(dateObserved) == null) return false; + + //type: must be "CensusObserved"; + if (type.compareTo("CensusObserved") != 0) return false; + + //more checks? + + return true; + } + + public static CensusObserved createCensusObserved(String data) { + + CensusObserved census = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + census = Utils.JSON2Object(obj.toString(), CensusObserved.class); + census.setLocation(pol); + census.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to CensusObserved: " + e.getMessage()); + } + + return census; + } + + + @Override + public String toString() { + return "{\"addressRegion\":\"" + addressRegion + "\", \"age\":\"" + age + "\", \"createdAt\":\"" + createdAt + + "\", \"currentEconomicStatus\":\"" + currentEconomicStatus + "\", \"dateObserved\":\"" + dateObserved + + "\", \"disabilityBenefits\":\"" + disabilityBenefits + "\", \"educationAllowances\":\"" + + educationAllowances + "\", \"employeeCashIncome\":\"" + employeeCashIncome + "\", \"gender\":\"" + + gender + "\", \"householdCSWeight\":\"" + householdCSWeight + "\", \"householdID\":\"" + householdID + + "\", \"hsize\":\"" + hsize + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"modifiedAt\":\"" + modifiedAt + "\", \"nationality\":\"" + nationality + "\", \"netIncome\":\"" + + netIncome + "\", \"oldAgeBenefits\":\"" + oldAgeBenefits + "\", \"personalCSWeight\":\"" + + personalCSWeight + "\", \"selfEmploymentLosses\":\"" + selfEmploymentLosses + + "\", \"sicknessBenefits\":\"" + sicknessBenefits + "\", \"source\":\"" + source + + "\", \"survivorBenefits\":\"" + survivorBenefits + "\", \"type\":\"" + type + + "\", \"unemploymentBenefits\":\"" + unemploymentBenefits + "\", \"context\":\"" + context + "\"}"; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..10f0e0495077bcf61de5bcd1ee2500ff9db0c75d --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationDataModel.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Population; + +public enum PopulationDataModel { + CENSUSOBSERVED, + POPULATIONOBSERVED +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationObserved.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..1c6ef08bf5e7293dacc44d0d81d5842d198c91ed --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Population/PopulationObserved.java @@ -0,0 +1,214 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Population; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.google.gson.FieldNamingPolicy; +import com.tecnalia.urbanite.storage.DataModel.Common.District; +import com.tecnalia.urbanite.storage.DataModel.Common.DoubleValue; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.MaleFemale; +import com.tecnalia.urbanite.storage.DataModel.Common.PopulationSummary; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class PopulationObserved { + + + + @Schema( description = "Unique identifier of the entity.", required = true) + private String id; + + + @Schema(description = "NGSI Entity type. It has to be 'CensusFlowObserved'.", required = true) + private String type; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A description of this item.", required = false) + private String description; + + + @Schema(description = "The date and time of this observation in ISO8601 UTC format.", required = false) + private String dateObserved; + + @Schema(description = "populationSummary", name = "population-summary", required = false) + private PopulationSummary populationSummary; + + @Schema(description = "populationAges", name = "population-ages", required = false) + private HashMap<String, MaleFemale> populationAges; + + @Schema(description = "districtsSummary", name = "districts-summary", required = false) + private HashMap<String, District> districtsSummary; + + @Schema(description = "educationPercentages", name = "education-percentages", required = false) + private HashMap<String, DoubleValue> educationPercentages; + + @Schema(description = "workingPeople", name = "working-people", required = false) + private HashMap<String, DoubleValue> workingPeople; + + + @Schema(hidden = true) + private List<String> context; + + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public PopulationSummary getPopulationSummary() { + return populationSummary; + } + + public void setPopulationSummary(PopulationSummary populationSummary) { + this.populationSummary = populationSummary; + } + + public HashMap<String, MaleFemale> getPopulationAges() { + return populationAges; + } + + public void setPopulationAges(HashMap<String, MaleFemale> populationAges) { + this.populationAges = populationAges; + } + + public HashMap<String, District> getDistrictsSummary() { + return districtsSummary; + } + + public void setDistrictsSummary(HashMap<String, District> districtsSummary) { + this.districtsSummary = districtsSummary; + } + + public HashMap<String, DoubleValue> getEducationPercentages() { + return educationPercentages; + } + + public void setEducationPercentages(HashMap<String, DoubleValue> educationPercentages) { + this.educationPercentages = educationPercentages; + } + + public HashMap<String, DoubleValue> getWorkingPeople() { + return workingPeople; + } + + public void setWorkingPeople(HashMap<String, DoubleValue> workingPeople) { + this.workingPeople = workingPeople; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type + if (id == null || type == null) return false; + + //DateObserved: valid date + if (dateObserved != null) + if (Utils.ISO2Date(dateObserved) == null) return false; + + + if (type.compareTo("PopulationObserved") != 0) return false; + + //more checks? + + return true; + } + + public static PopulationObserved createPopulationObserved(String data) { + + PopulationObserved census = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + census = Utils.JSON2Object(obj.toString(), PopulationObserved.class, FieldNamingPolicy.LOWER_CASE_WITH_DASHES); + + census.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to PopulationObserved: " + e.getMessage()); + } + + return census; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/SortingMode.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/SortingMode.java new file mode 100644 index 0000000000000000000000000000000000000000..5610abe785a2f67a04f68d7c5cd8a453ff122803 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/SortingMode.java @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum SortingMode { + ASC (1), + DESC (-1); + + private final int value; + + private SortingMode(int value) { + this.value = value; + } + + public int getOrder() { + return value; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/Calendar.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/Calendar.java new file mode 100644 index 0000000000000000000000000000000000000000..005a10b92e7b2b62a0ea7a92c3b6169e3badfdcc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/Calendar.java @@ -0,0 +1,193 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Time; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.EnumUtils; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class Calendar { + + private String id = null; + private String type = null; + private String city = null; + private LocationGeojson location; + private Integer year = null; + private List<String> days = new ArrayList<String>(); + private List<String> context = new ArrayList<String>(); + + private String createdAt; //from database + private String modifiedAt; //from database + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getCity() { + return city; + } + public void setCity(String city) { + this.city = city; + } + public LocationGeojson getLocation() { + return location; + } + public void setLocation(LocationGeojson location) { + this.location = location; + } + public Integer getYear() { + return year; + } + public void setYear(Integer year) { + this.year = year; + } + public List<String> getDays() { + return days; + } + public void setDays(List<String> days) { + this.days = days; + } + + public String getCreatedAt() { + return createdAt; + } + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + public String getModifiedAt() { + return modifiedAt; + } + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + + public List<String> getContext() { + return context; + } + public void setContext(List<String> context) { + this.context = context; + } + + public static Calendar createCalendar (String data) { + + try { + + JSONObject obj= new JSONObject(data); + + //location + LocationGeojson pol = null; + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + + + Calendar cal = Utils.JSON2Object(obj.toString(), Calendar.class); //without "location" + if (cal != null) + cal.setLocation(pol); + + //@context --> context + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + for (int i = 0; i < arrCont.length(); i++) { + cal.context.add(arrCont.getString(i)); + } + } + + + return cal; + + } catch (JSONException e) { + return null; + } + } + + public boolean isValid() { + + //mandatory fields + if (this.id == null + || this.type == null + || this.year == null + || days.isEmpty()) + return false; + + //type must be "DaySpecification" + if (type.compareTo("Calendar") != 0) return false; + + //city + if (this.city != null && EnumUtils.isValidEnum(City.class, city.toLowerCase()) == false) return false; + + //year + if (this.year <= 0) return false; + + return true; + } + + + + @Override + public String toString() { + return "{\"id\":\"" + id + "\", \"type\":\"" + type + "\", \"city\":\"" + city + "\", \"location\":\"" + + location + "\", \"year\":\"" + year + "\", \"days\":\"" + days + "\", \"@context\":\"" + context + + "\", \"createdAt\":\"" + createdAt + "\", \"modifiedAt\":\"" + modifiedAt + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/DaySpecification.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/DaySpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..843aebdf5bc2d89b2d8e15199ee34f7a2684632f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/DaySpecification.java @@ -0,0 +1,174 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Time; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.Utils.Utils; + + +public class DaySpecification { + + //@Schema(description = "Unique identifier", required = true, example = "urn:ngsi-ld:DaySpecification:Bilbao:2021_01_01") + private String id = null; + private String type = null; + private String date = null; + private String description = null; + private Integer workingDay = null; + private Integer schoolDay = null; + private Integer publicHoliday = null; + private Integer weekDay = null; + private List<String> context = new ArrayList<String>(); + + private String createdAt; //from database + private String modifiedAt; //from database + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getDate() { + return date; + } + public void setDate(String date) { + this.date = date; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public int getWorkingDay() { + return workingDay; + } + public void setWorkingDay(int workingDay) { + this.workingDay = workingDay; + } + public int getSchoolDay() { + return schoolDay; + } + public void setSchoolDay(int schoolDay) { + this.schoolDay = schoolDay; + } + public int getPublicHoliday() { + return publicHoliday; + } + public void setPublicHoliday(int publicHoliday) { + this.publicHoliday = publicHoliday; + } + public int getWeekDay() { + return weekDay; + } + public void setWeekDay(int weekDay) { + this.weekDay = weekDay; + } + public String getCreatedAt() { + return createdAt; + } + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + public String getModifiedAt() { + return modifiedAt; + } + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + public List<String> getContext() { + return context; + } + public void setContext(List<String> context) { + this.context = context; + } + + public static DaySpecification createDaySpecification(String data) { + + try { + + DaySpecification day = Utils.JSON2Object(data, DaySpecification.class); + + //@context --> context + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + for (int i = 0; i < arrCont.length(); i++) { + day.context.add(arrCont.getString(i)); + } + } + + return day; + } catch (JSONException e) { + return null; + } + } + + public boolean isValid() { + + //mandatory fields + if (this.id == null + || this.type == null + || this.date == null + || this.workingDay == null + || this.schoolDay == null + || this.publicHoliday == null + || this.weekDay == null) + return false; + + //type must be "DaySpecification" + if (type.compareTo("DaySpecification") != 0) return false; + + //valid date + try { + String[] sDate = this.date.split("-"); + Calendar cal = Calendar.getInstance(); + cal.setLenient(false); + cal.set(Integer.parseInt(sDate[0]), Integer.parseInt(sDate[1]) - 1 /*January: month 0*/, Integer.parseInt(sDate[2])); + Date d = cal.getTime(); //if it's not a valid date --> exception + } catch (Exception e) { + return false; + } + + //TODO: more ckecks: values 0 or 1 for workingDay, schoolDay, publicHoliday, and 1 to 7 for weekDay. + return true; + } + + @Override + public String toString() { + return "{\"id\":\"" + id + "\", \"type\":\"" + type + "\", \"date\":\"" + date + "\", \"description\":\"" + + description + "\", \"workingDay\":\"" + workingDay + "\", \"schoolDay\":\"" + schoolDay + + "\", \"publicHoliday\":\"" + publicHoliday + "\", \"weekDay\":\"" + weekDay + "\", \"createdAt\":\"" + + createdAt + "\", \"modifiedAt\":\"" + modifiedAt + "\", \"@context\":\"" + context + "\"}"; + } + + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/TimeDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/TimeDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..44f435b30207276ddde76f96cbe4685d45be9c6e --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Time/TimeDataModel.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Time; + +public enum TimeDataModel { + DAYSPECIFICATION, + CALENDAR +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTrip.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTrip.java new file mode 100644 index 0000000000000000000000000000000000000000..cf1411041c680fba2a81822fbf4318fe8813d8aa --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTrip.java @@ -0,0 +1,742 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TouristTrip; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.Audience; +import com.tecnalia.urbanite.storage.DataModel.Common.Category; +import com.tecnalia.urbanite.storage.DataModel.Common.CriticReview; +import com.tecnalia.urbanite.storage.DataModel.Common.Currency; +import com.tecnalia.urbanite.storage.DataModel.Common.ElectricTransport; +import com.tecnalia.urbanite.storage.DataModel.Common.Itinerary; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.OpeningHoursSpecification; +import com.tecnalia.urbanite.storage.DataModel.Common.PaymentAccepted; +import com.tecnalia.urbanite.storage.DataModel.Common.Pitch; +import com.tecnalia.urbanite.storage.DataModel.Common.PriceSpecification; +import com.tecnalia.urbanite.storage.DataModel.Common.RouteType; +import com.tecnalia.urbanite.storage.DataModel.Common.StarRatingDetail; +import com.tecnalia.urbanite.storage.DataModel.Common.SubCategory; +import com.tecnalia.urbanite.storage.DataModel.Common.TouristType; +import com.tecnalia.urbanite.storage.DataModel.Common.TransportService; +import com.tecnalia.urbanite.storage.DataModel.Common.TripSchedule; +import com.tecnalia.urbanite.storage.DataModel.Common.TripStatus; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TouristTrip { + @Schema(description = "Text or Link to the access plan to the Trip.", required = false) + private String accessPlan; + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "Higher level area to which this air quality measurement belongs to.", required = false) + private String areaServed; + + @Schema(description = "Type of public concerned by this Trip. A combination of Free text (family, adult, children, teenager, senior, allPublic, ...). Enum:''adult, allPublic, children, family, senior, teenager''", required = false) + private Audience audience; + + @Schema(description = "Category of the Trip. A combination of free text to remain flexible to a specific context is offered below as an initial repository or any other value needed by an application. enum:''excursion, gastronomy, history, museum, outdoorActivities, parksAndGardens, religiousWorship, shopping, wellness''", required = false) + private Category category; + + @Schema(description = "Specifies the URL to the official image or video of the Trip for more information.", required = false) + private String contentURL; + + @Schema(description = "Specifies the URL to the official image or video of the Trip for more information.", required = false) + private CriticReview criticReview; + + @Schema(description = "Currency accepted for payment if `TripFree` is False. A combination of a list of active codes defined in the model. [Norme ISO 4217](http://en.wikipedia.org/wiki/ISO_4217), [Crypto Currencies](https://en.wikipedia.org/wiki/List_of_cryptocurrencies) , [Exchange Trading System](https://en.wikipedia.org/wiki/Local_exchange_trading_system)", required = false) + private List<Currency> currencyAccepted; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Last official update of the data in ISO 8601 format", required = false) + private String dateLastReported; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "The duration of each show. The unit code (text) of measurement is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **HUR** represents **Hours**.", required = false) + private Double duration; + + @Schema(description = "List of the different types of electric transport proposed by the city. A combination of. Enum:''electricBicycle, electricCar, electricMotorBike, electricScooter''", required = false) + private List<ElectricTransport> electricTransport; + + @Schema(description = "End date and time in an ISO8601 UTC format", required = false) + private String endDate; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Free or paid Trip (True = Free / False = Paid).", required = false) + private Boolean isAccessibleForFree; + + @Schema(description = "Destinations or places that make up a trip. For a trip where destination order is important use ItemList to specify that order included in the trips.", required = false) + private List<Itinerary> itinerary; + + @Schema(description = "List of Formal language used during the Trip expressed from the IETF [BCP 47](https://tools.ietf.org/html/bcp47) standard", required = false) + private List<String> language; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon' ", required = true) + private LocationGeojson location; + + @Schema(description = "Name of the trip location.", required = false) + private String locationName; + + @Schema(description = "The total number of people who can attend to the Trip at that location.", required = false) + private Integer maximumAttendeeCapacity; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A structured value providing information about the opening hours of a place or a certain service inside a place", required = false) + private List<OpeningHoursSpecification> openingHoursSpecification; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "Accepted payment if `TripFree` is False. A combination of a list of active codes defined in the model. Enum:''Cash, CreditCard, CryptoCurrency, other''", required = false) + private List<PaymentAccepted> paymentAccepted; + + @Schema(description = "Pitch of the Trip. Each items have the format based on the [Internationalization (i18N) - W3C recommandation for multilanguage](https://www.w3.org/TR/json-ld/#string-internationalization) integrating all items in a single property (ex number 71). Each item is represented by a string with Language Value : Article Value. ", required = false) + private List<Pitch> pitch; + + @Schema(description = "A structured value representing a price or price range depending categories or public.", required = false) + private List<PriceSpecification> priceSpecification; + + @Schema(description = "Rating value of Trips. Usage guidelines: Use values from 0 to 10 depending on your standard. this is the average value of all detailed scores of `starRatingDetailed` attribute", required = false) + private Double ratingValueAverage; + + @Schema(description = "Reference to all the Point Of interest [PointOfInterest](https://github.com/smart-data-models/dataModel.PointOfInterest/blob/master/PointOfInterest/doc/spec.md) included in the trips. The POI list does not have a chronological order.", required = false) + private List<String> refPointOfInterest; + + @Schema(description = "List of the urban transports (subway, Bus, Tram, ...) available near the Trip according to the GFTS standard [STOP](https://developers.google.com/transit/gtfs/reference/#stopstxt). A combination of values. Enum:' bus, cableCar, cableTram, ferry, funicular, monorail, subway, train, tram, trolleybus'", required = false) + private List<RouteType> routeType; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "Trip header line, matches the text hook.", required = false) + private String slogan; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "Detailed star ratings which led to the average value expressed in the ratingValue. Instructions for use: A structured value from 1 to 10 occurrences (Stars) where each element is a string in the format: `NumberOfSTar`: Percent. ", required = false) + private StarRatingDetail starRatingDetailed; + + @Schema(description = "Start date and time in an ISO8601 UTC format", required = false) + private String startDate; + + @Schema(description = "Sub-category of the `category` attribute. A combination of free text to remain flexible to a specific context is offered below as an initial example or any other value needed by an application. ", required = false) + private SubCategory subCategory; + + @Schema(description = "Reference to a list of Minor Trips that are part of this major Trip", required = false) + private String subTrip; + + @Schema(description = "Property. Identifier format of any NGSI entity", required = false) + private String superTrip; + + @Schema(description = "A list of thematic as keywords", required = false) + private List<String> thematic; + + @Schema(description = "Title of the Trip.", required = false) + private String title; + + @Schema(description = "enumeration of different tourist types applicable to the TouristTrip", required = false) + private List<TouristType> touristType; + + @Schema(description = "List of private transport available near the Trip. In example taxi, uber, vtc, parkingShuttle", required = false) + private List<TransportService>transportServices; + + @Schema(description = "Min Price. The unit code (text) of measurement is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **EUR** represents **€uro**.", required = false) + private Double tripPriceFrom; + + @Schema(description = "Max Price. The unit code (text) of measurement is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **EUR** represents **€uro**.", required = false) + private Double tripPriceTo; + + @Schema(description = "Trip Schedule. This allows a schedule to be set over a repeated period of time used to describe an Trip that occurs regularly. In example nota in the beginning of the section for restriction to use this attribute.", required = false) + private List<TripSchedule> tripSchedule; + + @Schema(description = "Trip Status regarding this Trip. Enum:''cancelled, closed, finished, opened, postponed, rescheduled, scheduled, suspended''", required = true) + private TripStatus tripStatus; + + @Schema(description = "NGSI Entity type. Must be 'TouristDestination'.", required = true) + private String type; + + @Schema(description = "Link to the official website for more information.", required = true) + private String webSite; + + @Schema(description = "Access possible for Person with Reduced Mobility.", required = true) + private Boolean wheelChairAccessible; + + @Schema(description = "Custom field for Urbanite, id of the trip", required = true) + private String idTrip; + + @Schema(description = "Custom field for Urbanite, customer of the trip", required = true) + private String customer; + + + @Schema(hidden = true) + private List<String> context; + + public String getAccessPlan() { + return accessPlan; + } + + public void setAccessPlan(String accessPlan) { + this.accessPlan = accessPlan; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public Audience getAudience() { + return audience; + } + + public void setAudience(Audience audience) { + this.audience = audience; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public String getContentURL() { + return contentURL; + } + + public void setContentURL(String contentURL) { + this.contentURL = contentURL; + } + + public CriticReview getCriticReview() { + return criticReview; + } + + public void setCriticReview(CriticReview criticReview) { + this.criticReview = criticReview; + } + + public List<Currency> getCurrencyAccepted() { + return currencyAccepted; + } + + public void setCurrencyAccepted(List<Currency> currencyAccepted) { + this.currencyAccepted = currencyAccepted; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateLastReported() { + return dateLastReported; + } + + public void setDateLastReported(String dateLastReported) { + this.dateLastReported = dateLastReported; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Double getDuration() { + return duration; + } + + public void setDuration(Double duration) { + this.duration = duration; + } + + public List<ElectricTransport> getElectricTransport() { + return electricTransport; + } + + public void setElectricTransport(List<ElectricTransport> electricTransport) { + this.electricTransport = electricTransport; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Boolean getIsAccessibleForFree() { + return isAccessibleForFree; + } + + public void setIsAccessibleForFree(Boolean isAccessibleForFree) { + this.isAccessibleForFree = isAccessibleForFree; + } + + public List<Itinerary> getItinerary() { + return itinerary; + } + + public void setItinerary(List<Itinerary> itinerary) { + this.itinerary = itinerary; + } + + public List<String> getLanguage() { + return language; + } + + public void setLanguage(List<String> language) { + this.language = language; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getLocationName() { + return locationName; + } + + public void setLocationName(String locationName) { + this.locationName = locationName; + } + + public Integer getMaximumAttendeeCapacity() { + return maximumAttendeeCapacity; + } + + public void setMaximumAttendeeCapacity(Integer maximumAttendeeCapacity) { + this.maximumAttendeeCapacity = maximumAttendeeCapacity; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<OpeningHoursSpecification> getOpeningHoursSpecification() { + return openingHoursSpecification; + } + + public void setOpeningHoursSpecification(List<OpeningHoursSpecification> openingHoursSpecification) { + this.openingHoursSpecification = openingHoursSpecification; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public List<PaymentAccepted> getPaymentAccepted() { + return paymentAccepted; + } + + public void setPaymentAccepted(List<PaymentAccepted> paymentAccepted) { + this.paymentAccepted = paymentAccepted; + } + + public List<Pitch> getPitch() { + return pitch; + } + + public void setPitch(List<Pitch> pitch) { + this.pitch = pitch; + } + + public List<PriceSpecification> getPriceSpecification() { + return priceSpecification; + } + + public void setPriceSpecification(List<PriceSpecification> priceSpecification) { + this.priceSpecification = priceSpecification; + } + + public Double getRatingValueAverage() { + return ratingValueAverage; + } + + public void setRatingValueAverage(Double ratingValueAverage) { + this.ratingValueAverage = ratingValueAverage; + } + + public List<String> getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(List<String> refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public List<RouteType> getRouteType() { + return routeType; + } + + public void setRouteType(List<RouteType> routeType) { + this.routeType = routeType; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSlogan() { + return slogan; + } + + public void setSlogan(String slogan) { + this.slogan = slogan; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public StarRatingDetail getStarRatingDetailed() { + return starRatingDetailed; + } + + public void setStarRatingDetailed(StarRatingDetail starRatingDetailed) { + this.starRatingDetailed = starRatingDetailed; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public SubCategory getSubCategory() { + return subCategory; + } + + public void setSubCategory(SubCategory subCategory) { + this.subCategory = subCategory; + } + + public String getSubTrip() { + return subTrip; + } + + public void setSubTrip(String subTrip) { + this.subTrip = subTrip; + } + + public String getSuperTrip() { + return superTrip; + } + + public void setSuperTrip(String superTrip) { + this.superTrip = superTrip; + } + + public List<String> getThematic() { + return thematic; + } + + public void setThematic(List<String> thematic) { + this.thematic = thematic; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List<TouristType> getTouristType() { + return touristType; + } + + public void setTouristType(List<TouristType> touristType) { + this.touristType = touristType; + } + + public List<TransportService> getTransportServices() { + return transportServices; + } + + public void setTransportServices(List<TransportService> transportServices) { + this.transportServices = transportServices; + } + + public Double getTripPriceFrom() { + return tripPriceFrom; + } + + public void setTripPriceFrom(Double tripPriceFrom) { + this.tripPriceFrom = tripPriceFrom; + } + + public Double getTripPriceTo() { + return tripPriceTo; + } + + public void setTripPriceTo(Double tripPriceTo) { + this.tripPriceTo = tripPriceTo; + } + + public List<TripSchedule> getTripSchedule() { + return tripSchedule; + } + + public void setTripSchedule(List<TripSchedule> tripSchedule) { + this.tripSchedule = tripSchedule; + } + + public TripStatus getTripStatus() { + return tripStatus; + } + + public void setTripStatus(TripStatus tripStatus) { + this.tripStatus = tripStatus; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getWebSite() { + return webSite; + } + + public void setWebSite(String webSite) { + this.webSite = webSite; + } + + public Boolean getWheelChairAccessible() { + return wheelChairAccessible; + } + + public void setWheelChairAccessible(Boolean wheelChairAccessible) { + this.wheelChairAccessible = wheelChairAccessible; + } + + public String getIdTrip() { + return idTrip; + } + + public void setIdTrip(String idTrip) { + this.idTrip = idTrip; + } + + public String getCustomer() { + return customer; + } + + public void setCustomer(String customer) { + this.customer = customer; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public static TouristTrip createTouristTrip(String data) { + + TouristTrip tt = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + tt = Utils.JSON2Object(obj.toString(), TouristTrip.class); + tt.setLocation(pol); + tt.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to TouristTrip: " + e.getMessage()); + } + + return tt; + } + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id and type + if (id == null || type == null) return false; + + //Required dateObserved or dateObservedFrom + dateObservedTo + + + return true; + } + + @Override + public String toString() { + return "{\"webSite\":\"" + webSite + "\", \"address\":\"" + address + + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + areaServed + + "\", \"category\":\"" + category + "\", \"wheelChairAccessible\":\"" + wheelChairAccessible + "\", \"dataProvider\":\"" + dataProvider + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"location\":\"" + location + + "\", \"name\":\"" + name + "\", \"owner\":\"" + owner + + "\", \"tripStatus\":\"" + tripStatus + "\", \"seeAlso\":\"" + seeAlso + + "\", \"source\":\"" + source + "\", \"type\":\"" + type + "\", \"tripPriceFrom\":\"" + + tripPriceFrom + "\", \"tripPriceTo\":\"" + tripPriceTo + + "\", \"transportServices\":\"" + transportServices + "\"context\":\"" + + context + "\"}"; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTripDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTripDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..a555892c3156369ca25c52c02e606d5cf933ab59 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TouristTrip/TouristTripDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TouristTrip; + +public enum TouristTripDataModel { + TouristTrip +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStation.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStation.java new file mode 100644 index 0000000000000000000000000000000000000000..27a264c70c77bc2612b58ff618ed8e956ffdec52 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStation.java @@ -0,0 +1,512 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TransportStation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.ContactPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.Dimension; +import com.tecnalia.urbanite.storage.DataModel.Common.InstallationMode; +import com.tecnalia.urbanite.storage.DataModel.Common.Inventory; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.OpeningHoursSpecification; +import com.tecnalia.urbanite.storage.DataModel.Common.StationConnected; +import com.tecnalia.urbanite.storage.DataModel.Common.StationType; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TransportStation { + @Schema(description = "The mailing address", required = false) + private Address address; + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided", required = false) + private String areaServed; + + @Schema(description = "The details to contact with the item.", required = false) + private ContactPoint contactPoint; + + @Schema(description = "Name of the contracting authority.", required = false) + private String contractingAuthority; + + @Schema(description = "Name of the contracting company responsible for the exploitation of the station.", required = false) + private String contractingCompany; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "A timestamp which denotes the last time when the device successfully reported data. Date and time in an ISO8601 UTCformat.", required = true) + private String dateLastReported; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "A description of this item", required = false) + private String description; + + @Schema(description = "Global dimension. The format is structured by a sub-property of 3 items. The unit code (text) is given using the [UN/CEFACT Common Codes](http://wiki.goodrelations-vocabulary.org/Documentation/UN/CEFACT_Common_Codes). For instance, **MTR** represents Meters", required = false) + private Dimension dimension; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Location relative to the ground reference. Enum:''aerial, ground, underGround, underSea''", required = false) + private InstallationMode installationMode; + + @Schema(description = "General data mapping only for `locationType` = 0, 1, 3, 4. The format is structured by a sub-property of 4 items.", required = false) + private Inventory inventory; + + @Schema(description = "Floor on which the location is located. Numerical index associated with the floor. Indicates the relative position of this stage in relation to the others. The index 0 indicates the ground floor. The floors above ground level are indicated by positive indices, and the underground stages by negative indices.", required = false) + private Integer levelId; + + @Schema(description = "The geographical shape associated to this entity encoded as GeoJSON LineString or MultiLineString.", required = false) + private LocationGeojson location; + + @Schema(description = "Link to the GTFS standard repository describing the different location [Location Type]. 0 Stop or platform (place where users get on or off in a public transport vehicle). 1 Station (area or physical structure comprising one or more platforms). 2 Entrance or Exit (place where users can enter / exit a station from the street). 3 Generic intersection (location in a station that doesn''t correspond to any other `location_type` value). 4 Boarding area of a specific location on a platform where users can get on / off in a vehicle.", required = false) + private String locationType; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A structured value providing information about the opening hours of a place or a certain service inside a place", required = false) + private List<OpeningHoursSpecification> openingHoursSpecification; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s)", required = false) + private List<String> owner; + + @Schema(description = "Link to the GTFS standard repository describing the different link between Station and Platform [Parent STATION]. Case ''1'' location_type = 0 (Stop / platform ), the parent_station field contains the ID of a station. Case ''2'' location_type = 1 (Station), this field must be empty. Case ''3'' location_type = 2 (Input / output) or location_type = 3 (generic intersection), the parent_station field contains the ID of a station location_type = 1. Case ''4'' location_type = 4 (boarding area), the parent_station field contains the ID of a platform.", required = false) + private String parentStation; + + @Schema(description = "Platform identifier for a platform type stop `location_type` = 0 when the stop is in a station.", required = false) + private Integer platformCode; + + @Schema(description = "A reference to a point of interest associated to this observation.", required = false) + private String refPointOfInterest; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "Connections possible from this station. A structured value from 0 to N occurrences where each items is a string in the format `stationType` : [List of Lines connected, separated by a comma]. Enum:''aerialLift, bus, cableTram, ferry, funicular, monorail, rail, subway, train, tram, trolleybus''", required = false) + private List<StationConnected> stationConnected; + + @Schema(description = "Type of transport station. Enum:''aerialLift, bus, cableTram, ferry, funicular, monorail, rail, subway, trolleybus, tram''", required = false) + private List<StationType> stationType; + + @Schema(description = "NGSI Entity type. Must be 'TransportStation'.", required = true) + private String type; + + @Schema(description = "Link to the official website for more information.", required = false) + private String webSite; + + @Schema(description = "Access possible for Person with Reduced Mobility. For stops without parents 0 no information is available regarding the accessibility of the stop. 1 some vehicles at this stop can board a PMR user. 2 PRM user cannot board at this stop. For a stop that is part of a station 0 the stop inherits the wheelchair_boarding behavior of the parent station, if it is filled in. 1 lanes provide wheelchair access to the stop / platform from outside the station. 2 no lane provides wheelchair access to the stop / platform from outside the station. For station inputs / outputs 0 the station entry inherits the wheelchair_boarding behavior of the main station, if specified. 1 the station entrance is wheelchair accessible. 2 no wheelchair accessible route connects the station entrance to the stops / platforms.", required = false) + private Integer wheelChairAccessible; + + + @Schema(description = "Pricing zone of the station.", required = true) + private String zoneId; + + @Schema(hidden = true) + private List<String> context; + + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public ContactPoint getContactPoint() { + return contactPoint; + } + + public void setContactPoint(ContactPoint contactPoint) { + this.contactPoint = contactPoint; + } + + public String getContractingAuthority() { + return contractingAuthority; + } + + public void setContractingAuthority(String contractingAuthority) { + this.contractingAuthority = contractingAuthority; + } + + public String getContractingCompany() { + return contractingCompany; + } + + public void setContractingCompany(String contractingCompany) { + this.contractingCompany = contractingCompany; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateLastReported() { + return dateLastReported; + } + + public void setDateLastReported(String dateLastReported) { + this.dateLastReported = dateLastReported; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Dimension getDimension() { + return dimension; + } + + public void setDimension(Dimension dimension) { + this.dimension = dimension; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public InstallationMode getInstallationMode() { + return installationMode; + } + + public void setInstallationMode(InstallationMode installationMode) { + this.installationMode = installationMode; + } + + public Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + public Integer getLevelId() { + return levelId; + } + + public void setLevelId(Integer levelId) { + this.levelId = levelId; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getLocationType() { + return locationType; + } + + public void setLocationType(String locationType) { + this.locationType = locationType; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<OpeningHoursSpecification> getOpeningHoursSpecification() { + return openingHoursSpecification; + } + + public void setOpeningHoursSpecification(List<OpeningHoursSpecification> openingHoursSpecification) { + this.openingHoursSpecification = openingHoursSpecification; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getParentStation() { + return parentStation; + } + + public void setParentStation(String parentStation) { + this.parentStation = parentStation; + } + + public Integer getPlatformCode() { + return platformCode; + } + + public void setPlatformCode(Integer platformCode) { + this.platformCode = platformCode; + } + + public String getRefPointOfInterest() { + return refPointOfInterest; + } + + public void setRefPointOfInterest(String refPointOfInterest) { + this.refPointOfInterest = refPointOfInterest; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public List<StationConnected> getStationConnected() { + return stationConnected; + } + + public void setStationConnected(List<StationConnected> stationConnected) { + this.stationConnected = stationConnected; + } + + public List<StationType> getStationType() { + return stationType; + } + + public void setStationType(List<StationType> stationType) { + this.stationType = stationType; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getWebSite() { + return webSite; + } + + public void setWebSite(String webSite) { + this.webSite = webSite; + } + + public Integer getWheelChairAccessible() { + return wheelChairAccessible; + } + + public void setWheelChairAccessible(Integer wheelChairAccessible) { + this.wheelChairAccessible = wheelChairAccessible; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + public static TransportStation createTransportStation(String data) throws Exception { + + TransportStation ts = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + ts = Utils.JSON2Object(obj.toString(), TransportStation.class); + ts.setLocation(pol); + ts.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to TransportStation: " + e.getMessage()); + throw e; + } + + return ts; + } + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id and type + if (id == null || type == null) return false; + + if (!type.equals("TransportStation")) return false; + + + return true; + } + @Override + public String toString() { + + String retorno= "{\"address\":" + address.toString() + ", \"alternateName\":\"" + alternateName + + "\", \"areaServed\":\"" + areaServed + "\", \"contactPoint\":" + contactPoint.toString() + + ", \"contractingAuthority\":\"" + contractingAuthority + "\", \"contractingCompany\":\"" + contractingCompany + "\", \"dataProvider\":\"" + dataProvider + + "\", \"dateCreated\":\"" + dateCreated + "\", \"dateLastReported\":\"" + dateLastReported + + "\", \"dateModified\":\"" + dateModified + + "\", \"description\":\"" + description + "\", \"dimension\":" + dimension.toString() + ", \"id\":\"" + id + + "\", \"installationMode\":\"" + installationMode + "\", \"inventory\":" + inventory.toString() + + ", \"levelId\":" + levelId + " \"location\":" + location.toString() + + ", \"locationType\":\"" + locationType + "\", \"name\":\"" + name + + ", \"openingHoursSpecification\":["; + for (OpeningHoursSpecification pt:openingHoursSpecification) retorno=retorno+""+pt.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"owner\":["; + + for (String o:owner) retorno=retorno+""+o.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"parentStation\":\"" + parentStation + + "\", \"platformCode\":\"" + platformCode + "\", \"refPointOfInterest\":" + refPointOfInterest + + "\", \"seeAlso\":["; + for (String o:seeAlso) retorno=retorno+""+o.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"source\":\"" + source + + "\", \"stationConnected\":["; + for (StationConnected pt:stationConnected) retorno=retorno+""+pt.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"stationType\":["; + + for (StationType pt:stationType) retorno=retorno+""+pt.toString()+","; + retorno = retorno.substring(0, retorno.length()-1); + retorno = retorno+"], \"type\":\"" + type + + + "\", \"webSite\":\"" + webSite + "\", \"wheelChairAccessible\":" + wheelChairAccessible.toString() + +",\"zoneId\":"+zoneId+"\"}"; + return retorno; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStationDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStationDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..33a657ea01abeaab35f21dfe2837c78f9ca78d52 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TransportStation/TransportStationDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.TransportStation; + +public enum TransportStationDataModel { + TransportStation +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/LaneDirection.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/LaneDirection.java new file mode 100644 index 0000000000000000000000000000000000000000..6840d593df94786fb8cc4b83a828e4f73ae7d18b --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/LaneDirection.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +public enum LaneDirection { + forward, + backward +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/ODMatrixValue.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/ODMatrixValue.java new file mode 100644 index 0000000000000000000000000000000000000000..1a53c55cf32eda99b399750cdc277928cd2c8dcc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/ODMatrixValue.java @@ -0,0 +1,86 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class ODMatrixValue { + + + @Schema(description = ".", required = false) + private String arrivesTo; + + @Schema(description = ".", required = false) + private String departsFrom; + + @Schema(description = ".", required = false) + private Integer time; + + @Schema(description = ".", required = false) + private Double volume; + + @Schema(description = ".", required = false) + private Double volumePercentage; + + public String getArrivesTo() { + return arrivesTo; + } + + public void setArrivesTo(String arrivesTo) { + this.arrivesTo = arrivesTo; + } + + public String getDepartsFrom() { + return departsFrom; + } + + public void setDepartsFrom(String departsFrom) { + this.departsFrom = departsFrom; + } + + public Integer getTime() { + return time; + } + + public void setTime(Integer time) { + this.time = time; + } + + public Double getVolume() { + return volume; + } + + public void setVolume(Double volume) { + this.volume = volume; + } + + public Double getVolumePercentage() { + return volumePercentage; + } + + public void setVolumePercentage(Double volumePercentage) { + this.volumePercentage = volumePercentage; + } + + @Override + public String toString() { + return "{\"arrivesTo\":\"" + arrivesTo + "\", \"departsFrom\":\"" + departsFrom + "\", \"time\":" + time + + ", \"volume\":" + volume + ", \"volumePercentage\":" + volumePercentage + "}"; + } + + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/OriginDestinationMatrix.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/OriginDestinationMatrix.java new file mode 100644 index 0000000000000000000000000000000000000000..d60db4421a7936a7bea2596d485c52767e3a5f1e --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/OriginDestinationMatrix.java @@ -0,0 +1,268 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.DataModel.Population.CensusObserved; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class OriginDestinationMatrix { + + //Description: 'Origin - Destination Matrices' + //REF: https://git.code.tecnalia.com/urbanite/public/-/blob/main/datamodels/ODMatrix-ngsi.jsonld + + @Schema(description = "Aggregation type.", required = false) + private String aggregationType; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "NGSI Entity type. It has to be 'OriginDestinationMatrix'.", required = true) + private String type; + + @Schema(description = "Category.", required = false) + private String category; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String createdAt; + + @Schema(description = "End date.", required = false) + private String endDate; + + @Schema(description = "End period.", required = false) + private String endPeriod; + + @Schema(description = "Zones.", required = false) + private List<ODMatrixValue> matrixData; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String modifiedAt; + + @Schema(description = "Start date.", required = true) + private String startDate; + + @Schema(description = "Start period.", required = false) + private String startPeriod; + + @Schema(description = "Travel mode.", required = false) + private String travelMode; + + @Schema(description = "Zones.", required = false) + private String zones; + + @Schema(hidden = true) + private List<String> context; + + public String getAggregationType() { + return aggregationType; + } + + public void setAggregationType(String aggregationType) { + this.aggregationType = aggregationType; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getEndPeriod() { + return endPeriod; + } + + public void setEndPeriod(String endPeriod) { + this.endPeriod = endPeriod; + } + + public List<ODMatrixValue> getMatrixData() { + return matrixData; + } + + public void setMatrixData(List<ODMatrixValue> matrixData) { + this.matrixData = matrixData; + } + + public String getModifiedAt() { + return modifiedAt; + } + + public void setModifiedAt(String modifiedAt) { + this.modifiedAt = modifiedAt; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getStartPeriod() { + return startPeriod; + } + + public void setStartPeriod(String startPeriod) { + this.startPeriod = startPeriod; + } + + public String getTravelMode() { + return travelMode; + } + + public void setTravelMode(String travelMode) { + this.travelMode = travelMode; + } + + public String getZones() { + return zones; + } + + public void setZones(String zones) { + this.zones = zones; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + public boolean isValid() { + + //Required fields: id, type, dateObserved + if (id == null || type == null || startDate == null) return false; + + //type: must be "OriginDestinationMatrix"; + if (type.compareTo("OriginDestinationMatrix") != 0) return false; + + + //startDate: valid date (YYYY-MM-DD format) + if (Utils.checkValidDate(startDate) == false) return false; + + //endDate: if present, valid date (YYYY-MM-DD format) + if (endDate != null && Utils.checkValidDate(endDate) == false) return false; + + //startPeriod: if present, valid time (HH:MM or HH:MM:SS format) + if (startPeriod != null && Utils.checkValidTime(startPeriod) == false) return false; + + //endPeriod: if present, valid time (HH:MM or HH:MM:SS format) + if (endPeriod != null && Utils.checkValidTime(endPeriod) == false) return false; + + //more checks? + + return true; + } + + public static OriginDestinationMatrix createOriginDestinationMatrix(String data) { + + OriginDestinationMatrix od = null; + + try { + + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + od = Utils.JSON2Object(obj.toString(), OriginDestinationMatrix.class); + + //put start and end periods in format HH:MM:SS + String stPer = od.getStartPeriod(); + if (stPer != null) + if (stPer.length() == 5) od.setStartPeriod(stPer + ":00"); + String endPer = od.getEndPeriod(); + if (endPer != null) + if (endPer.length() == 5) od.setEndPeriod(endPer + ":00"); + + od.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to CensusObserved: " + e.getMessage()); + } + + return od; + } + + @Override + public String toString() { + return "{\"aggregationType\":\"" + aggregationType + "\", \"id\":\"" + id + "\", \"type\":\"" + type + + "\", \"category\":\"" + category + "\", \"createdAt\":\"" + createdAt + "\", \"endDate\":\"" + endDate + + "\", \"endPeriod\":\"" + endPeriod + "\", \"matrixData\":" + matrixData + ", \"modifiedAt\":\"" + + modifiedAt + "\", \"startDate\":\"" + startDate + "\", \"startPeriod\":\"" + startPeriod + + "\", \"travelMode\":\"" + travelMode + "\", \"zones\":\"" + zones + "\", \"context\":\"" + context + + "\"}"; + } + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TrafficFlowObserved.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TrafficFlowObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..c19365188fb3db2a3345f2e35384f19cea46398b --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TrafficFlowObserved.java @@ -0,0 +1,517 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class TrafficFlowObserved { + + // Description: 'An observation of traffic flow conditions at a certain place + // and time.' + // REF: + // https://github.com/smart-data-models/dataModel.Transportation/tree/master/TrafficFlowObserved + // TODO: add examples to @Schemas + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided.", required = false) + private String areaServed; + + @Schema(description = "Average gap distance (meters) between consecutive vehicles.", required = false) + private Double averageGapDistance; + + @Schema(description = "Average headway time (seconds). Headway time is the time ellapsed between two consecutive vehicles.", required = false) + private Double averageHeadwayTime; + + @Schema(description = "Average length (meters) of the vehicles transiting during.", required = false) + private Double averageVehicleLength; + + @Schema(description = "Average speed (Km/h) of the vehicles transiting during the observation period.", required = false) + private Double averageVehicleSpeed; + + @Schema(description = "Flags whether there was a traffic congestion during the observation period in the referred lane. The absence of this attribute means no traffic congestion.", required = false) + private Boolean congested; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String dateModified; + + @Schema(description = "The date and time of this observation in ISO8601 UTC format. It can be represented by an specific time instant or by an ISO8601 interval. As a workaround for the lack of support of Orion Context Broker for datetime intervals, it can be used two separate attributes: `dateObservedFrom`, `dateObservedTo`. [DateTime](https://schema.org/DateTime) or an ISO8601 interval represented as [Text](https://schema.org/Text).", required = true) + private String dateObserved; + + @Schema(description = "Observation period start date and time. See `dateObserved`", required = false) + private String dateObservedFrom; + + @Schema(description = "Observation period end date and time. See `dateObserved`", required = false) + private String dateObservedTo; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Total number of vehicles detected during this observation period.", required = false) + private Double intensity; + + @Schema(description = "Usual direction of travel in the lane referred by this observation. This attribute is useful when the observation is not referencing any road segment, allowing to know the direction of travel of the traffic flow observed. Enum:forward, backward''. See RoadSegment for a description of the semantics of these values.", required = false) + private String laneDirection; + + @Schema(description = "Lane identifier. Lane identification is done using the conventions defined by RoadSegment entity which are based on [OpenStreetMap](http://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right).", required = false) + private Double laneId; + + @Schema(description = "GeoJSON Geometry.", required = false) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "Fraction of the observation time where a vehicle has been occupying the observed lane.", required = false) + private Double occupancy; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "Concerned road segment on which the observation has been made. Reference to an entity of type RoadSegment.", required = false) + private String refRoadSegment; // It's a reference! + + @Schema(description = "Flags whether traffic in the lane was reversed during the observation period. The absence of this attribute means no lane reversion.", required = false) + private Boolean reversedLane; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "sense of traffic", required = false) + private String sense; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "NGSI Entity type. It has to be 'TrafficFlowObserved'.", required = true) + private String type; + + @Schema(description = "It allows to specify a sub type of `vehicleType`, eg if the `vehicleType` is set to `Lorry` the `vehicleSubType` may be `OGV1` or `OGV2` to convey more information about the exact type of vehicle.", required = false) + private String vehicleSubType; + + @Schema(description = "Type of vehicle from the point of view of its structural characteristics. Enum:''agriculturalVehicle, bicycle, bus, minibus, car, caravan, tram, tanker, carWithCaravan, carWithTrailer, lorry, moped, motorcycle, motorcycleWithSideCar, motorscooter, trailer, van, constructionOrMaintenanceVehicle, trolley, binTrolley, sweepingMachine, cleaningTrolley''.", required = false) + private VehicleTypeEnum vehicleType; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public double getAverageGapDistance() { + return averageGapDistance; + } + + public void setAverageGapDistance(double averageGapDistance) { + this.averageGapDistance = averageGapDistance; + } + + public double getAverageHeadwayTime() { + return averageHeadwayTime; + } + + public void setAverageHeadwayTime(double averageHeadwayTime) { + this.averageHeadwayTime = averageHeadwayTime; + } + + public double getAverageVehicleLength() { + return averageVehicleLength; + } + + public void setAverageVehicleLength(double averageVehicleLength) { + this.averageVehicleLength = averageVehicleLength; + } + + public double getAverageVehicleSpeed() { + return averageVehicleSpeed; + } + + public void setAverageVehicleSpeed(double averageVehicleSpeed) { + this.averageVehicleSpeed = averageVehicleSpeed; + } + + public boolean isCongested() { + return congested; + } + + public void setCongested(boolean congested) { + this.congested = congested; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDateObservedFrom() { + return dateObservedFrom; + } + + public void setDateObservedFrom(String dateObservedFrom) { + this.dateObservedFrom = dateObservedFrom; + } + + public String getDateObservedTo() { + return dateObservedTo; + } + + public void setDateObservedTo(String dateObservedTo) { + this.dateObservedTo = dateObservedTo; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public double getIntensity() { + return intensity; + } + + public void setIntensity(double intensity) { + this.intensity = intensity; + } + + public String getLaneDirection() { + return laneDirection; + } + + public void setLaneDirection(String laneDirection) { + this.laneDirection = laneDirection; + } + + public double getLaneId() { + return laneId; + } + + public void setLaneId(double laneId) { + this.laneId = laneId; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getOccupancy() { + return occupancy; + } + + public void setOccupancy(double occupancy) { + this.occupancy = occupancy; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public String getRefRoadSegment() { + return refRoadSegment; + } + + public void setRefRoadSegment(String refRoadSegment) { + this.refRoadSegment = refRoadSegment; + } + + public boolean isReversedLane() { + return reversedLane; + } + + public void setReversedLane(boolean reversedLane) { + this.reversedLane = reversedLane; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public String getSense() { + return sense; + } + + public void setSense(String sense) { + this.sense = sense; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getVehicleSubType() { + return vehicleSubType; + } + + public void setVehicleSubType(String vehicleSubType) { + this.vehicleSubType = vehicleSubType; + } + + public VehicleTypeEnum getVehicleType() { + return vehicleType; + } + + public void setVehicleType(VehicleTypeEnum vehicleType) { + this.vehicleType = vehicleType; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + @Schema(hidden = true) + public boolean isValid() { + + // Required fields: id and type + if (id == null || type == null) + return false; + + // Required dateObserved or dateObservedFrom + dateObservedTo + if (dateObserved == null) { + if (dateObservedFrom == null || dateObservedTo == null) + return false; + else { + // valid dates + if (Utils.ISO2Date(dateObservedFrom) == null) + return false; + if (Utils.ISO2Date(dateObservedTo) == null) + return false; + } + } + else { + // valid date + if (Utils.ISO2Date(dateObserved) == null) + return false; + } + // type: must be "TrafficFlowObserved"; + if (type.compareTo("TrafficFlowObserved") != 0) + return false; + + // Minimum/maximum values + if (averageGapDistance != null && averageGapDistance < 0) + return false; + if (averageHeadwayTime != null && averageHeadwayTime < 0) + return false; + if (averageVehicleLength != null && averageVehicleLength < 0) + return false; + if (averageVehicleSpeed != null && averageVehicleSpeed < 0) + return false; + if (intensity != null && intensity < 0) + return false; + if (laneId != null && laneId < 1) + return false; + if (occupancy != null && (occupancy < 0 || occupancy > 1)) + return false; + + return true; + } + + public static TrafficFlowObserved createTrafficFlowObserved(String data) { + + TrafficFlowObserved tf = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add(arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + tf = Utils.JSON2Object(obj.toString(), TrafficFlowObserved.class); + tf.setLocation(pol); + tf.setContext(cnt); + + } catch (Exception e) { + System.out + .println(" Exception converting JSON [" + data + "] to TrafficFlowObserved: " + e.getMessage()); + } + + return tf; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + + areaServed + "\", \"averageGapDistance\":\"" + averageGapDistance + "\", \"averageHeadwayTime\":\"" + + averageHeadwayTime + "\", \"averageVehicleLength\":\"" + averageVehicleLength + + "\", \"averageVehicleSpeed\":\"" + averageVehicleSpeed + "\", \"congested\":\"" + congested + + "\", \"dataProvider\":\"" + dataProvider + "\", \"dateCreated\":\"" + dateCreated + + "\", \"dateModified\":\"" + dateModified + "\", \"dateObserved\":\"" + dateObserved + + "\", \"dateObservedFrom\":\"" + dateObservedFrom + "\", \"dateObservedTo\":\"" + dateObservedTo + + "\", \"description\":\"" + description + "\", \"id\":\"" + id + "\", \"intensity\":\"" + intensity + + "\", \"laneDirection\":\"" + laneDirection + "\", \"laneId\":\"" + laneId + "\", \"location\":\"" + + location + "\", \"name\":\"" + name + "\", \"occupancy\":\"" + occupancy + "\", \"owner\":\"" + owner + + "\", \"refRoadSegment\":\"" + refRoadSegment + "\", \"reversedLane\":\"" + reversedLane + + "\", \"seeAlso\":\"" + seeAlso + "\", \"source\":\"" + source + "\", \"type\":\"" + type + + "\", \"sense\":\"" + sense + "\", \"vehicleSubType\":\"" + vehicleSubType + "\", \"vehicleType\":\"" + + vehicleType + "\", \"context\":\"" + context + "\"}"; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TransportationDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TransportationDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..db8dc972a4eafcfe40d01a0116f2ddd74251a948 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/TransportationDataModel.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +public enum TransportationDataModel { + TRAFFICFLOWOBSERVED, + //CROWDFLOWOBSERVED, + ORIGINDESTINATIONMATRIX, + VEHICLEOBSERVED +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/Vehicle.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/Vehicle.java new file mode 100644 index 0000000000000000000000000000000000000000..2416af7c1d15d45b9dcb6001f7455b68db9301ca --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/Vehicle.java @@ -0,0 +1,220 @@ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; +import org.springframework.util.ObjectUtils; + +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class Vehicle { + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "NGSI Entity type. It has to be 'Vehicle'.", required = true) + private String type; + + @Schema(description = " Type of vehicle from the point of view of its structural characteristics.", required = true) + private String vehicleType ; + + @Schema(description = "GeoJSON Geometry.", required = false) + private LocationGeojson location; + + @Schema(description = "Denotes the magnitude of the horizontal component of the vehicle's current velocity and is specified in Kilometers per Hour. If provided, the value of the speed attribute must be a non-negative real number. -1 MAY be used if speed is transiently unknown for some reason", required = false) + private Double speed; + + @Schema(description = "Entity creation timestamp (allocated by the storage platform).", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity (allocated by the storage platform).", required = false) + private String dateModified; + + @Schema(description = "Last reported time of observation", required = true) + private String observationDateTime; + + @Schema(description = "Denotes the direction of travel of the vehicle and is specified in decimal degrees, where 0 <= heading < 360, counting clockwise relative to the true north. If the vehicle is stationary (i.e. the value of the speed attribute is 0), then the value of the heading attribute must be equal to -1", required = false) + private Double heading; + + @Schema(hidden = true) + private List<String> context; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getVehicleType() { + return vehicleType; + } + + public void setVehicleType(String vehicleType) { + this.vehicleType = vehicleType; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public Double getSpeed() { + return speed; + } + + public void setSpeed(Double speed) { + this.speed = speed; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getObservationDateTime() { + return observationDateTime; + } + + public void setObservationDateTime(String observationDateTime) { + this.observationDateTime = observationDateTime; + } + + public Double getHeading() { + return heading; + } + + public void setHeading(Double heading) { + this.heading = heading; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + @Schema(hidden = true) + public boolean isValid() { + + // Required fields: id and type + if (id == null || type == null || observationDateTime == null) + return false; + if (Utils.checkValidDate(observationDateTime) == false) return false; + + if (!type.equals("Vehicle")) return false; + + if (!ObjectUtils.containsConstant(VehicleTypeEnum.values(),vehicleType,true )) return false; + + + return true; + } + + public static Vehicle createVehicle(String data) { + + Vehicle tf = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add(arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + tf = Utils.JSON2Object(obj.toString(), Vehicle.class); + tf.setLocation(pol); + tf.setContext(cnt); + + } catch (Exception e) { + System.out + .println(" Exception converting JSON [" + data + "] to Vehicle: " + e.getMessage()); + } + + return tf; + } + + @Override + public String toString() { + return "{}"; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/VehicleTypeEnum.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/VehicleTypeEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..82c260b958cf44fc604476e97308c771b0968342 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Transportation/VehicleTypeEnum.java @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Transportation; + +public enum VehicleTypeEnum { + agriculturalVehicle, + bicycle, + eBike, + bus, + minibus, + car, + caravan, + tram, + tanker, + carWithCaravan, + carWithTrailer, + lorry, + moped, + motorcycle, + motorcycleWithSideCar, + motorscooter, + trailer, + van, + constructionOrMaintenanceVehicle, + trolley, + binTrolley, + sweepingMachine, + cleaningTrolley, + pedestrian +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TypeOfLocation.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TypeOfLocation.java new file mode 100644 index 0000000000000000000000000000000000000000..5885f9037afc20578d22044a03f3d40c614661ca --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/TypeOfLocation.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel; + +public enum TypeOfLocation { + indoor, + outdoor +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/PressureTendency.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/PressureTendency.java new file mode 100644 index 0000000000000000000000000000000000000000..3676c46b5f5e30e2e37ec7383211f14e836af18c --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/PressureTendency.java @@ -0,0 +1,22 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Weather; + +public enum PressureTendency { + falling, + raising, + steady +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherDataModel.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherDataModel.java new file mode 100644 index 0000000000000000000000000000000000000000..bdffb37865ad7f86c0e20f5dd98709e2ef2738dc --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherDataModel.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Weather; + +public enum WeatherDataModel { + WEATHEROBSERVED +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherObserved.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherObserved.java new file mode 100644 index 0000000000000000000000000000000000000000..cf74fbfad63dbcbb478ac2c0579e3163fb8563ec --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/DataModel/Weather/WeatherObserved.java @@ -0,0 +1,498 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.DataModel.Weather; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.EnumUtils; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.DataModel.Common.Address; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationGeojson; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiLineString; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationMultiPolygon; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPoint; +import com.tecnalia.urbanite.storage.DataModel.Common.LocationPolygon; +import com.tecnalia.urbanite.storage.Utils.Utils; + +import io.swagger.v3.oas.annotations.media.Schema; + +public class WeatherObserved { + + /* + TODO: + There's a mismatch between the definition and the examples. Examples have some fields that are not present in the definition: + atmosphericPressure + illuminance + stationCode and stationName (could be part of the referenced device?) + windSpeed + windDirection + Fields atmosphericPressure, windSpeed and windDirection can be relevant, so they are added to the original model + */ + + + @Schema(description = "The mailing address.", required = false) + private Address address; + + @Schema(description = "An alternative name for this item.", required = false) + private String alternateName; + + @Schema(description = "The geographic area where a service or offered item is provided.", required = false) + private String areaServed; + + @Schema(description = "Atmospheric pressure of the item.", required = false) + private Double atmosphericPressure; + + @Schema(description = "A sequence of characters identifying the provider of the harmonised data entity.", required = false) + private String dataProvider; + + @Schema(description = "Entity creation timestamp. This will usually be allocated by the storage platform.", required = false) + private String dateCreated; + + @Schema(description = "Timestamp of the last modification of the entity. This will usually be allocated by the storage platform.", required = false) + private String dateModified; + + @Schema(description = "Date of the observed entity defined by the user, in ISO8601 UTCformat.", required = true) + private String dateObserved; + + @Schema(description = "A description of this item.", required = false) + private String description; + + @Schema(description = "The dew point encoded as a number.", required = false) + private Double dewPoint; + + @Schema(description = "Temperature appreciation of the item.", required = false) + private Double feelsLikesTemperature; + + @Schema(description = "Unique identifier of the entity.", required = true) + private String id; + + @Schema(description = "Geojson reference to the item. It can be Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon.", required = true) + private LocationGeojson location; + + @Schema(description = "The name of this item.", required = false) + private String name; + + @Schema(description = "A List containing a JSON encoded sequence of characters referencing the unique Ids of the owner(s).", required = false) + private List<String> owner; + + @Schema(description = "Amount of water rain registered. Units: 'Liters per square meter'.", required = false) + private Double precipitation; + + @Schema(description = "Is the pressure rising or falling? It can be expressed in quantitative terms or qualitative terms.", required = false) + private Object pressureTendency; + + @Schema(description = "A reference to the device(s) which captured this observation.", required = false) + private String refDevice; + + @Schema(description = "Humidity in the Air.", required = false) + private Double relativeHumidity; + + @Schema(description = "List of uri pointing to additional resources about the item.", required = false) + private List<String> seeAlso; + + @Schema(description = "The snow height observed by generic snow depth measurement sensors, expressed in centimeters.", required = false) + private Double snowHeight; + + @Schema(description = "The solar radiation observed measured in Watts per square.", required = false) + private Double solarRadiation; + + @Schema(description = "A sequence of characters giving the original source of the entity data as a URL. Recommended to be the fully qualified domain name of the source provider, or the URL to the source object.", required = false) + private String source; + + @Schema(description = "The water level surface elevation observed by Hydrometric measurement sensors, namely a [Stream Gauge](https://en.wikipedia.org/wiki/Stream_gauge) expressed in centimeters.", required = false) + private Double streamGauge; + + @Schema(description = "Temperature of the item.", required = false) + private Double temperature; + + @Schema(description = "NGSI Entity type. Must be 'WeatherObserved'.", required = true) + private String type; + + @Schema(description = "The maximum UV index for the period, based on the World Health Organization''s UV Index measure. [http://www.who.int/uv/intersunprogramme/activities/uv_index/en/](http://www.who.int/uv/intersunprogramme/activities/uv_index/en/).", required = false) + private Double uvIndexMax; + + @Schema(description = "Direction of the wind.", required = false) + private Double windDirection; + + @Schema(description = "Intensity of the wind.", required = false) + private Double windSpeed; + + @Schema(hidden = true) + private List<String> context; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getAlternateName() { + return alternateName; + } + + public void setAlternateName(String alternateName) { + this.alternateName = alternateName; + } + + public String getAreaServed() { + return areaServed; + } + + public void setAreaServed(String areaServed) { + this.areaServed = areaServed; + } + + public Double getAtmosphericPressure() { + return atmosphericPressure; + } + + public void setAtmosphericPressure(Double atmosphericPressure) { + this.atmosphericPressure = atmosphericPressure; + } + + public String getDataProvider() { + return dataProvider; + } + + public void setDataProvider(String dataProvider) { + this.dataProvider = dataProvider; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getDateObserved() { + return dateObserved; + } + + public void setDateObserved(String dateObserved) { + this.dateObserved = dateObserved; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Double getDewPoint() { + return dewPoint; + } + + public void setDewPoint(Double dewPoint) { + this.dewPoint = dewPoint; + } + + public Double getFeelsLikesTemperature() { + return feelsLikesTemperature; + } + + public void setFeelsLikesTemperature(Double feelsLikesTemperature) { + this.feelsLikesTemperature = feelsLikesTemperature; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public LocationGeojson getLocation() { + return location; + } + + public void setLocation(LocationGeojson location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<String> getOwner() { + return owner; + } + + public void setOwner(List<String> owner) { + this.owner = owner; + } + + public Double getPrecipitation() { + return precipitation; + } + + public void setPrecipitation(Double precipitation) { + this.precipitation = precipitation; + } + + public Object getPressureTendency() { + return pressureTendency; + } + + public void setPressureTendency(PressureTendency pressureTendency) { + this.pressureTendency = pressureTendency; + } + public void setPressureTendency(Double pressureTendency) { + this.pressureTendency = pressureTendency; + } + + public String getRefDevice() { + return refDevice; + } + + public void setRefDevice(String refDevice) { + this.refDevice = refDevice; + } + + public Double getRelativeHumidity() { + return relativeHumidity; + } + + public void setRelativeHumidity(Double relativeHumidity) { + this.relativeHumidity = relativeHumidity; + } + + public List<String> getSeeAlso() { + return seeAlso; + } + + public void setSeeAlso(List<String> seeAlso) { + this.seeAlso = seeAlso; + } + + public Double getSnowHeight() { + return snowHeight; + } + + public void setSnowHeight(Double snowHeight) { + this.snowHeight = snowHeight; + } + + public Double getSolarRadiation() { + return solarRadiation; + } + + public void setSolarRadiation(Double solarRadiation) { + this.solarRadiation = solarRadiation; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Double getStreamGauge() { + return streamGauge; + } + + public void setStreamGauge(Double streamGauge) { + this.streamGauge = streamGauge; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Double getUvIndexMax() { + return uvIndexMax; + } + + public void setUvIndexMax(Double uvIndexMax) { + this.uvIndexMax = uvIndexMax; + } + + public Double getWindDirection() { + return windDirection; + } + + public void setWindDirection(Double windDirection) { + this.windDirection = windDirection; + } + + public Double getWindSpeed() { + return windSpeed; + } + + public void setWindSpeed(Double windSpeed) { + this.windSpeed = windSpeed; + } + + public List<String> getContext() { + return context; + } + + public void setContext(List<String> context) { + this.context = context; + } + + + + @Schema(hidden = true) + public boolean isValid() { + + //Required fields: id, type, dateObserved, location + if (id == null || type == null || dateObserved == null || location == null) return false; + + //DateObserved: valid date + if (Utils.ISO2Date(dateObserved) == null) return false; + + //type: must be "WeatherObserved"; + if (type.compareTo("WeatherObserved") != 0) return false; + + + + //Minimum/maximum values + if (atmosphericPressure != null && atmosphericPressure < 0) return false; + if (precipitation != null && precipitation < 0) return false; + if (snowHeight != null && snowHeight < 0) return false; + if (solarRadiation != null && solarRadiation < 0) return false; + if (streamGauge != null && streamGauge < 0) return false; + if (relativeHumidity!= null && (relativeHumidity < 0 || relativeHumidity > 1)) return false; + if (uvIndexMax != null && uvIndexMax < 1) return false; + if (windDirection!= null && (windDirection < -180 || windDirection > 180)) return false; + if (windSpeed != null && windSpeed < 0) return false; + + //pressureTendency: numeric or "PressureTendency" + if (pressureTendency != null) { + if (pressureTendency instanceof Number == false && pressureTendency instanceof String == false ) return false; + else { + if (pressureTendency instanceof String) { + if (EnumUtils.isValidEnum(PressureTendency.class, pressureTendency.toString()) == false) return false; + } + } + } + + //all ok + return true; + } + + public static WeatherObserved createWeatherObserved(String data) { + + WeatherObserved weather = null; + + try { + + LocationGeojson pol = null; + List<String> cnt = null; + + JSONObject obj = new JSONObject(data); + if (obj.has("@context")) { + JSONArray arrCont = obj.getJSONArray("@context"); + cnt = new ArrayList<String>(); + for (int i = 0; i < arrCont.length(); i++) { + cnt.add (arrCont.getString(i)); + } + } + + if (obj.has("location")) { + JSONObject location = obj.getJSONObject("location"); + String tp = location.getString("type").toUpperCase(); + obj.remove("location"); + switch (tp) { + case "POINT": + pol = Utils.JSON2Object(location.toString(), LocationPoint.class); + break; + case "LINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationLineString.class); + break; + case "POLYGON": + pol = Utils.JSON2Object(location.toString(), LocationPolygon.class); + break; + case "MULTIPOINT": + pol = Utils.JSON2Object(location.toString(), LocationMultiPoint.class); + break; + case "MULTILINESTRING": + pol = Utils.JSON2Object(location.toString(), LocationMultiLineString.class); + break; + case "MULTIPOLYGON": + pol = Utils.JSON2Object(location.toString(), LocationMultiPolygon.class); + break; + } + } + + weather = Utils.JSON2Object(obj.toString(), WeatherObserved.class); + weather.setLocation(pol); + weather.setContext(cnt); + + } catch (Exception e) { + System.out.println(" Exception converting JSON [" + data + "] to WeatherObserved: " + e.getMessage()); + } + + return weather; + } + + @Override + public String toString() { + return "{\"address\":\"" + address + "\", \"alternateName\":\"" + alternateName + "\", \"areaServed\":\"" + + areaServed + "\", \"atmosphericPressure\":\"" + atmosphericPressure + "\", \"dataProvider\":\"" + + dataProvider + "\", \"dateCreated\":\"" + dateCreated + "\", \"dateModified\":\"" + dateModified + + "\", \"dateObserved\":\"" + dateObserved + "\", \"description\":\"" + description + + "\", \"dewPoint\":\"" + dewPoint + "\", \"feelsLikesTemperature\":\"" + feelsLikesTemperature + + "\", \"id\":\"" + id + "\", \"location\":\"" + location + "\", \"name\":\"" + name + + "\", \"owner\":\"" + owner + "\", \"precipitation\":\"" + precipitation + + "\", \"pressureTendency\":\"" + pressureTendency + "\", \"refDevice\":\"" + refDevice + + "\", \"relativeHumidity\":\"" + relativeHumidity + "\", \"seeAlso\":\"" + seeAlso + + "\", \"snowHeight\":\"" + snowHeight + "\", \"solarRadiation\":\"" + solarRadiation + + "\", \"source\":\"" + source + "\", \"streamGauge\":\"" + streamGauge + "\", \"temperature\":\"" + + temperature + "\", \"type\":\"" + type + "\", \"uvIndexMax\":\"" + uvIndexMax + + "\", \"windDirection\":\"" + windDirection + "\", \"windSpeed\":\"" + windSpeed + "\", \"context\":\"" + + context + "\"}"; + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Response.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Response.java new file mode 100644 index 0000000000000000000000000000000000000000..1385be069c22935e2eed496c8aee4ee5746aefdb --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Response.java @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage; + +import org.springframework.http.HttpStatus; + +public class Response +{ + private final HttpStatus status; + private final String data; + + public Response(HttpStatus status, String data) + { + this.status = status; + this.data = data; + } + + public HttpStatus getStatus() { + return status; + } + + public String getData() { + return data; + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/JsonDateTimeConverter.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/JsonDateTimeConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..345f1066e6dd21cc29a7f34b000f8a9cf0172095 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/JsonDateTimeConverter.java @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.Utils; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +import org.bson.json.Converter; +import org.bson.json.StrictJsonWriter; + +public class JsonDateTimeConverter implements Converter<Long> { + + static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.of("UTC")); + + @Override + public void convert(Long value, StrictJsonWriter writer) { + try { + Instant instant = new Date(value).toInstant(); + String s = DATE_TIME_FORMATTER.format(instant); + writer.writeString(s); + } catch (Exception e) { + System.out.println(" Fail to convert " + value + " to JSON date: " + e.getMessage()); + } + } + +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/StringToEnumConverter.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/StringToEnumConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..2c4cda1ae2e33749e3508317b9d97030aeba74d6 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/StringToEnumConverter.java @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.Utils; + +import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +import org.springframework.core.convert.converter.Converter; + +public class StringToEnumConverter implements Converter<String, AggregatorEnum> +{ + @Override + public AggregatorEnum convert(String source) { + return AggregatorEnum.valueOf(source.toUpperCase()); + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/Utils.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..3598876d4f6fd76c9f11903e84454db7cf112bd5 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/Utils/Utils.java @@ -0,0 +1,360 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.Utils; + +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.riot.RDFFormat; +import org.apache.jena.vocabulary.DCTerms; +import org.bson.BsonValue; +import org.bson.Document; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mongodb.DBRef; +import com.tecnalia.urbanite.storage.APIResponse; + +public class Utils { + + private static DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static DateFormat dfOnlyDate = new SimpleDateFormat("yyyy-MM-dd"); + private static DateFormat dfOnlyTime = new SimpleDateFormat("HH:mm:ss"); + private static DateFormat dfOnlyTime2 = new SimpleDateFormat("HH:mm"); + + public static <T> T JSON2Object(String input, Class<T> clazz) { + + try { + Gson g = new Gson(); + T p = g.fromJson(input, clazz); + return p; + } catch (Exception ex) { + System.err.println(" Exception converting JSON [" + input + "] to Object [" + clazz.getName() + "]: " + ex.getMessage()); + throw ex; + } + } + public static <T> T JSON2Object(String input, Class<T> clazz,FieldNamingPolicy policy) { + + try { + Gson g = new GsonBuilder() + .setFieldNamingPolicy(policy) + .create() ; + T p = g.fromJson(input, clazz); + return p; + } catch (Exception ex) { + System.err.println(" Exception converting JSON [" + input + "] to Object [" + clazz.getName() + "]: " + ex.getMessage()); + throw ex; + } + } + + public static String Object2JSON (Object o) { + try { + Gson g = new Gson(); + return g.toJson(o); + } catch (Exception ex) { + System.err.println(" Exception converting object [" + o.getClass().getName() + "] to String: " + ex.getMessage()); + return null; + } + } + public static String Object2JSON (Object o, FieldNamingPolicy policy) { + try { + Gson g = new GsonBuilder() + .setFieldNamingPolicy(policy) + .create() ; + return g.toJson(o); + } catch (Exception ex) { + System.err.println(" Exception converting object [" + o.getClass().getName() + "] to String: " + ex.getMessage()); + return null; + } + } + public static String Date2ISO(Date input) { + try { + String dt = df.format(input); + return dt; + } catch (Exception ex) { + System.err.println(" Exception converting date " + input + " to ISO8601 UTC format: " + ex.getMessage()); + return ""; + } + } + + public static int getDateYear(Date date) { + if (date == null) return 0; + LocalDateTime lstartDate =LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC")); + return lstartDate.getYear(); + + } + public static int getDateMonth(Date date) { + if (date == null) return 0; + LocalDateTime lstartDate =LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC")); + return lstartDate.getMonthValue(); + + } + public static Date ISO2Date(String input) { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + try { + df.setTimeZone(TimeZone.getTimeZone("UTC")); + Date dt = df.parse(input); + return dt; + } catch (Exception ex) { + try { + df2.setTimeZone(TimeZone.getTimeZone("UTC")); + Date dt = df2.parse(input); + return dt; + } catch (Exception ex2) { + System.err.println(" Date [" + input + "] is not ISO 8601 format: " + ex2.getMessage()); + return null; + } + } + } + + public static String addJsonLdModels(String jsonld1, String jsonld2) { + if (jsonld1 == null || jsonld1.isEmpty()) { + return jsonld2; + } + + if (jsonld2 == null || jsonld2.isEmpty()) { + return jsonld1; + } + + Model m1 = ModelFactory.createDefaultModel(); + try (StringReader reader = new StringReader(jsonld1)) { + m1.read(reader, null, "JSON-LD"); + } + + try (StringReader reader = new StringReader(jsonld2)) { + m1.read(reader, null, "JSON-LD"); + } + + + try { + updateIssuedDates(m1); + updateModifiedDates(m1); + } catch (Exception ex) { + System.err.println ("Error updating issued dates"); + } + + + StringWriter out = new StringWriter(); + RDFDataMgr.write(out, m1, RDFFormat.JSONLD_PRETTY); + + return out.toString(); + } + + public static Model updateIssuedDates(Model m) throws Exception { + // Get the property and the subject + Property issued = m.getProperty(DCTerms.getURI() + "issued"); + String sd = null; + + // Get all statements/triples of the form (****, issued, ***) + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + Date issDate = null; + Statement issDateStmt = null; + for (Statement st:m.listStatements(null, issued, sd).toList()) { + + if (issDate ==null) { + issDateStmt = st; + issDate = formatter.parse(st.getString()); + } else { + Date scur = formatter.parse(st.getString()); + + //for issued date we keep the oldest date + if (scur.before(issDate)) { + //delete from model + m.remove(issDateStmt); + issDateStmt = st; + issDate = scur; + } else { + m.remove(st); + } + } + + } + + return m; + } + + public static Model updateModifiedDates(Model m) throws Exception { + // Get the property and the subject + Property modified = m.getProperty(DCTerms.getURI() + "modified"); + String sd = null; + + // Get all statements/triples of the form (****, modified, ***) + SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + Date issDate = null; + Statement issDateStmt = null; + for (Statement st:m.listStatements(null, modified, sd).toList()) { + + if (issDate ==null) { + issDateStmt = st; + issDate = formatter.parse(st.getString()); + } else { + Date scur = formatter.parse(st.getString()); + + //for modified date we keep the latest date + if (scur.after(issDate)) { + //delete from model + m.remove(issDateStmt); + issDateStmt = st; + issDate = scur; + } else { + m.remove(st); + } + } + + } + + return m; + } + + public static ResponseEntity<Object> formatResponse(APIResponse res) { + return formatResponse(res, false); + } + + public static ResponseEntity<Object> formatResponse(APIResponse res, boolean returnAsArray) { + + if (res.getStatus() != HttpStatus.OK) { + JSONObject jsError = new JSONObject(); + try { + jsError.put("Error", res.getError()); + } catch (JSONException e) { + //shouldn't be any error, it's just creating a JSON! + } + return new ResponseEntity<Object>(jsError.toString(), new HttpHeaders(), res.getStatus()); + } + else { + List<JSONObject> data = res.getData(); + if (returnAsArray) + return new ResponseEntity<Object>(data.toString(), new HttpHeaders(), res.getStatus()); + else { + //return a Object if only one element present + if (data.size() == 1) + return new ResponseEntity<Object>(data.get(0).toString(), new HttpHeaders(), res.getStatus()); + else + return new ResponseEntity<Object>(data.toString(), new HttpHeaders(), res.getStatus()); + } + } + } + + public static Object BsonValue2JavaType(BsonValue value) { + switch (value.getBsonType()) { + case INT32: + return value.asInt32().getValue(); + case INT64: + return value.asInt64().getValue(); + case STRING: + return value.asString().getValue(); + case DECIMAL128: + return value.asDecimal128().doubleValue(); + case DOUBLE: + return value.asDouble().getValue(); + case BOOLEAN: + return value.asBoolean().getValue(); + case OBJECT_ID: + return value.asObjectId().getValue(); + case DB_POINTER: + return new DBRef(value.asDBPointer().getNamespace(), value.asDBPointer().getId()); + case BINARY: + return value.asBinary().getData(); + case DATE_TIME: + return new Date(value.asDateTime().getValue()); + case SYMBOL: + return value.asSymbol().getSymbol(); + case ARRAY: + return value.asArray().toArray(); + case DOCUMENT: + return Document.parse(value.asDocument().toJson()); + default: + return value; + } + } + + public static <T> boolean typeHasFields(Class<T> type, List<String> fields) { + + if (fields != null && fields.isEmpty() == false) { + Field[] allTypeFields = type.getDeclaredFields(); + + List<String> lstFieldNames = new ArrayList<String>(); + for (Field f: allTypeFields) + lstFieldNames.add(f.getName()); + + for (String fieldName : fields) { + if (lstFieldNames.contains(fieldName) == false) + return false; + } + } + return true; + } + + public static <T> boolean typeHasFieldsJSON(Class<T> type, JSONObject fields) { + + List<String> lstFieldNames = new ArrayList<String>(); + Iterator<String> keys = fields.keys(); + while(keys.hasNext()) + lstFieldNames.add(keys.next()); + + return typeHasFields(type, lstFieldNames); + } + + public static boolean checkValidDate(String input) { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + try { + Date dt = dfOnlyDate.parse(input); + return true; + } catch (Exception ex) { + System.err.println(" Date [" + input + "] is not ISO 8601 format: " + ex.getMessage()); + return false; + } + } + + public static boolean checkValidTime(String input) { + + try { + Date dt = dfOnlyTime.parse(input); + return true; + } catch (Exception ex) { + try { + Date dt = dfOnlyTime2.parse(input); + return true; + } catch (Exception ex2) { + System.err.println(" Date [" + input + "] is not ISO 8601 format: " + ex2.getMessage()); + return false; + } + } + + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AggregatorController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AggregatorController.java new file mode 100644 index 0000000000000000000000000000000000000000..ff5947f011355cd920a6113f9e55b253f9909da5 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AggregatorController.java @@ -0,0 +1,68 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import com.tecnalia.opentsdb.client.OpentsdbClient; +import com.tecnalia.opentsdb.client.query.MetriQueryBuilder; +import com.tecnalia.opentsdb.client.query.QueryBuilder; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.Response; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import java.util.Objects; + +public class AggregatorController implements IAggregatorController +{ + private final Logger logger = LoggerFactory.getLogger(AggregatorController.class); + + @Override + public Response aggregate(City city, String compoundMetric, Date start, Date end, AggregatorEnum aggregator, String downsample, Map<String,String> tags) + { + Response response; + + try + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + HttpResponse httpResponse = opentsdbClient.Get( new QueryBuilder() + .addStart( Objects.requireNonNull((start)).getTime() /1000) + .addEnd( Objects.requireNonNull((end)).getTime() /1000) + .addMetricQuery(new MetriQueryBuilder() + .addAggregator(aggregator.name()).addMetric(compoundMetric).addDownsample(downsample).addTags(tags) + .build()) + .build()); + + response = new Response(HttpStatus.valueOf(httpResponse.getStatusLine().getStatusCode()), EntityUtils.toString(httpResponse.getEntity())); + + } + catch (IOException ex) + { + logger.error(ex.getMessage(), ex); + response = new Response(HttpStatus.INTERNAL_SERVER_ERROR, "It could not connect with the opentsdb server."); + } + + return response; + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AirQualityObservedController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AirQualityObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..5d7feb8060f7749dc02aa1608f89dc89c371ed5f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/AirQualityObservedController.java @@ -0,0 +1,428 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.AirQualityObserved; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class AirQualityObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(AirQualityObservedController.class); + + public AirQualityObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("AirQualityObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> airColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + airColls.add(collectionName); + } + + for (int i = 0; i < arrData.length(); i++) { + AirQualityObserved record = AirQualityObserved.createAirQualityObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (airColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + doc.append("dateObserved", dateObs); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + /* + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + JSONObject oIns = new JSONObject(); + JSONObject oNoIns = new JSONObject(); + JSONObject oUpd = new JSONObject(); + try { + oIns.put("inserted", arrInserted); + oNoIns.put("notInserted", arrNotInserted); + oUpd.put("updated", arrUpdated); + lstRes.add(oIns); + lstRes.add(oNoIns); + lstRes.add(oUpd); + } catch (JSONException e) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + */ + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("AirQualityObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Air Quality Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Air Quality Observation' objects)"); + } + + logger.debug("AirQualityObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "AirQualityObserved", AirQualityObserved.class); + } + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("AirQualityObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + AirQualityObserved record = AirQualityObserved.createAirQualityObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + String collectionName = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Air Quality Observation' object)"); + } + + logger.debug("AirQualityObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "AirQualityObserved"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "AirQualityObserved", AirQualityObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"co\": 500,\r\n" + + " \"coLevel\": \"moderate\",\r\n" + + " \"no\": 45,\r\n" + + " \"no2\": 69,\r\n" + + " \"nox\": 139,\r\n" + + " \"so2\": 11,\r\n" + + " \"address\": {\r\n" + + " \"addressCountry\": \"ES\",\r\n" + + " \"addressLocality\": \"Madrid\",\r\n" + + " \"streetAddress\": \"Plaza de Espa\\u00f1a\",\r\n" + + " \"type\": \"PostalAddress\"\r\n" + + " },\r\n" + + " \"airQualityIndex\": 65,\r\n" + + " \"airQualityLevel\": \"moderate\",\r\n" + + " \"areaServed\": \"Brooklands\",\r\n" + + " \"dateObserved\": \"2016-03-15T11:00:00Z\",\r\n" + + " \"id\": \"urn:ngsi-ld:AirQualityObserved:Madrid-AmbientObserved-28079004-2016-03-15T11:00:00\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -3.712247222222222,\r\n" + + " 40.423852777777775\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"typeOfLocation\": \"outdoor\",\r\n" + + " \"precipitation\": 0,\r\n" + + " \"relativeHumidity\": 0.54,\r\n" + + " \"reliability\": 0.7,\r\n" + + " \"source\": \"http://datos.madrid.es\",\r\n" + + " \"temperature\": 12.2,\r\n" + + " \"type\": \"AirQualityObserved\",\r\n" + + " \"windDirection\": 166,\r\n" + + " \"windSpeed\": 0.64\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("AirQualityObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "AirQualityObserved",AirQualityObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "AirQualityObserved",AirQualityObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (EnvironmentDataModel.AIRQUALITYOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "AirQualityObserved"); + + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CalendarController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CalendarController.java new file mode 100644 index 0000000000000000000000000000000000000000..ab0d43e694444fd2711a7ac442843d1d43775f53 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CalendarController.java @@ -0,0 +1,1019 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Time.Calendar; +import com.tecnalia.urbanite.storage.DataModel.Time.DaySpecification; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class CalendarController extends GenericController implements IGenericController{ + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(CalendarController.class); + + public CalendarController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + logger.debug("Calendar::insertData(" + city + ")::IN"); + + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + //check data + if (data.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data not found."); + } + else { + + try { + JSONArray jsArrData = new JSONArray(data); + + if (jsArrData.length() != 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String DayCollectionName = (TimeDataModel.DAYSPECIFICATION+ "_" + city).toLowerCase(); + String CalCollectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + + MongoCollection<Document> dayColl = database.getCollection(DayCollectionName); + MongoCollection<Document> calColl = database.getCollection(CalCollectionName); + + //separate DaySpecification and Calendar objects + Map<String, DaySpecification> mapDaySpecs = new HashMap<>(); + //List<DaySpecification> arrDaySpecs = new ArrayList<DaySpecification>(); + List<Calendar> arrCalendars = new ArrayList<Calendar>(); + + for (int i = 0; i < jsArrData.length(); i++) { + + JSONObject jsData = jsArrData.getJSONObject(i); + String type = jsData.getString("type"); + + if (type != null && type.isEmpty() == false) { + + if (type.compareToIgnoreCase("DaySpecification") == 0) { + DaySpecification day = DaySpecification.createDaySpecification(jsData.toString()); + if (day!= null) { + if (day.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", day.getId()); + o.put("reason", "Wrong input data [DaySpecification]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else + mapDaySpecs.put(day.getId(), day); + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [DaySpecification]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else if (type.compareToIgnoreCase("Calendar") == 0) { + Calendar cal = Calendar.createCalendar(jsData.toString()); + if (cal != null) { + if (cal.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else + arrCalendars.add(cal); + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [Calendar]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' must be 'Calendar' or 'DaySpecification'"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' not found"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } + + + /* Here we have: + * mapDaySpecs: map of valid DaySpecification objects + * arrCalendars: list of valid Calendar objects + * First, we'll check (for each calendar) if all it's days are in the input data or already present in database + * Next, we'll try to insert/update only the days of the calendar. If there's no error, the calendar itself will be inserted/updated + */ + + for (Calendar cal: arrCalendars) { + //check days + List<String> missingDays = new ArrayList<String>(); + List<String> calDays = cal.getDays(); + for (String dayId: calDays) { + //day in input data? + if (mapDaySpecs.containsKey(dayId) == false) { + //not in input data. In database? + if (dayColl.find(Filters.eq("_id", dayId)).first() == null) + missingDays.add(dayId); + //else exists + } //else will be inserted or updated + } + + if (missingDays.size() > 0) { + //some day not found --> error + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]: days " + missingDays + " not found." ); + arrNotInserted.put(o); + } catch (JSONException ex) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON: " + ex.getMessage()); + } + } + else { + //all it's days are present in input data or database --> insert/update them + List<String> errorDays = new ArrayList<String>(); + for (String dayId: calDays) { + DaySpecification daySpec = mapDaySpecs.get(dayId); + if (daySpec != null) { + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(daySpec)); + //need to replace "id" for "_id" + doc.put("_id", daySpec.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", daySpec.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + dayColl.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrInserted.put(o); + mapDaySpecs.remove(dayId); + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = dayColl.find(Filters.eq("_id", daySpec.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + dayColl.replaceOne(Filters.eq("_id", daySpec.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrUpdated.put(o); + mapDaySpecs.remove(dayId); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + } + } + else { + errorDays.add(daySpec.getId()); + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + + } + } + } + } + + for (String dayId : mapDaySpecs.keySet()) { + JSONObject o = new JSONObject(); + try { + o.put("id", dayId); + o.put("reason", "Unreferenced DaySpecification."); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + // errors? + if (errorDays.isEmpty()) { + //insert calendar + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(cal)); + //need to replace "id" for "_id" + doc.put("_id", cal.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", cal.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + calColl.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", cal.getId()); + arrInserted.put(o); + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = calColl.find(Filters.eq("_id", cal.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + calColl.replaceOne(Filters.eq("_id", cal.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", cal.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Some of the days could not be inserted in database"); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Calendar::insertData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + /* + JSONObject oIns = new JSONObject(); + JSONObject oNoIns = new JSONObject(); + JSONObject oUpd = new JSONObject(); + try { + oIns.put("inserted", arrInserted); + oNoIns.put("notInserted", arrNotInserted); + oUpd.put("updated", arrUpdated); + lstRes.add(oIns); + lstRes.add(oNoIns); + lstRes.add(oUpd); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + */ + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Calendar::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is empty."); + } + + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + logger.debug("Calendar::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + logger.debug("Calendar::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(Calendar.class, filters)) { + //check the return fields + if (Utils.typeHasFields(Calendar.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + DateFormat dateFormat = new SimpleDateFormat("yyyy"); + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) { + int yearStart = Integer.parseInt(dateFormat.format(startDate)); + dateRange.put("$gte", yearStart); + } + if (endDate != null) { + int yearEnd= Integer.parseInt(dateFormat.format(endDate)); + dateRange.put("$lte", yearEnd); + } + if (dateRange.isEmpty() == false) queryFilters.append("year", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("Calendar::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort= new BasicDBObject(); + //querySort.append("year", 1); + querySort.append("year",sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and year + if (returnFields.contains("year") == false) returnFields.add("year"); + projection = Projections.fields(Projections.include(returnFields)); + } + + FindIterable<Document> cursor; + if (limit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(limit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + //get days of the calendar + Calendar cal = Calendar.createCalendar(sDoc); + if (cal != null) { + List<JSONObject> arrDays = getDaysOfCalendar(database, city, cal.getDays()); + retData.addAll(arrDays); + } + + } catch (JSONException e) { + logger.error("Calendar::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.calendar + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.calendar + "' data model's fields."); + } + logger.debug("Calendar::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("Calendar::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + //check data + if (data.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data not found."); + } + else { + + try { + JSONArray jsArrData = new JSONArray(data); + + if (jsArrData.length() != 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String DayCollectionName = (TimeDataModel.DAYSPECIFICATION+ "_" + city).toLowerCase(); + String CalCollectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + + MongoCollection<Document> dayColl = database.getCollection(DayCollectionName); + MongoCollection<Document> calColl = database.getCollection(CalCollectionName); + + //separate DaySpecification and Calendar objects + Map<String, DaySpecification> mapDaySpecs = new HashMap<>(); + + //we can have only one calendar (with the id) + Calendar cal = null; + + for (int i = 0; i < jsArrData.length(); i++) { + + JSONObject jsData = jsArrData.getJSONObject(i); + String type = jsData.getString("type"); + + if (type != null && type.isEmpty() == false) { + + if (type.compareToIgnoreCase("DaySpecification") == 0) { + DaySpecification day = DaySpecification.createDaySpecification(jsData.toString()); + if (day!= null) { + if (day.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", day.getId()); + o.put("reason", "Wrong input data [DaySpecification]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else + mapDaySpecs.put(day.getId(), day); + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [DaySpecification]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else if (type.compareToIgnoreCase("Calendar") == 0) { + Calendar calData = Calendar.createCalendar(jsData.toString()); + if (calData != null) { + if (calData.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + //check id + if (calData.getId().compareToIgnoreCase(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } else + cal = calData; + } + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data [Calendar]"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' must be 'Calendar' or 'DaySpecification'"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.get("id")); + o.put("reason", "Wrong input data: field 'type' not found"); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + } + + //check if calendar exists: + Document existingCalendar = calColl.find(Filters.eq("_id", id)).first(); + if (existingCalendar == null) { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } else { + + /* Here we have: + * mapDaySpecs: map of valid DaySpecification objects + * cal: data of the calendar object to be updated + * First, we'll check if all the days of the calendar to be updated are in the input data or already present in database + * Next, we'll try to insert/update only the days of the calendar. If there's no error, the calendar itself will be updated + */ + + //check days + List<String> missingDays = new ArrayList<String>(); + List<String> calDays = cal.getDays(); + for (String dayId: calDays) { + //day in input data? + if (mapDaySpecs.containsKey(dayId) == false) { + //not in input data. In database? + if (dayColl.find(Filters.eq("_id", dayId)).first() == null) + missingDays.add(dayId); + //else exists + } //else will be inserted or updated + } + + if (missingDays.size() > 0) { + //some day not found --> error + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Wrong input data [Calendar]: days " + missingDays + " not found." ); + arrNotInserted.put(o); + } catch (JSONException ex) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON: " + ex.getMessage()); + } + } + else { + //all it's days are present in input data or database --> insert/update them + List<String> errorDays = new ArrayList<String>(); + for (String dayId: calDays) { + DaySpecification daySpec = mapDaySpecs.get(dayId); + if (daySpec != null) { + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(daySpec)); + //need to replace "id" for "_id" + doc.put("_id", daySpec.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", daySpec.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + dayColl.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrInserted.put(o); + mapDaySpecs.remove(dayId); + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = dayColl.find(Filters.eq("_id", daySpec.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + dayColl.replaceOne(Filters.eq("_id", daySpec.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrUpdated.put(o); + mapDaySpecs.remove(dayId); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + } + } + else { + errorDays.add(daySpec.getId()); + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + + } + } + } + } + + for (String dayId : mapDaySpecs.keySet()) { + JSONObject o = new JSONObject(); + try { + o.put("id", dayId); + o.put("reason", "Unreferenced DaySpecification."); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + // errors? + if (errorDays.isEmpty()) { + //update calendar + + try { + + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(cal)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", cal.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existingCalendar.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + + calColl.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + //need to put "id" + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + try { + JSONObject jElem= new JSONObject(sDoc); + o.put("updatedData", jElem); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + logger.error("Calendar::updateData(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", cal.getId()); + o.put("reason", "Some of the days could not be inserted in database"); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Calendar::updateData(" + city +"). Error creating JSON: " + e1.getMessage()); + } + } + + } + + } + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is empty."); + } + + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + logger.debug("Calendar::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "Calendar"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "year", collectionName, "Calendar", Calendar.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + String example = "{\r\n" + + " \"example\": [\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:Calendar:Bilbao:2015\",\r\n" + + " \"type\": \"Calendar\",\r\n" + + " \"city\": \"Bilbao\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -2.93609619140625,\r\n" + + " 43.26345626603949\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"year\": 2015,\r\n" + + " \"days\": [\r\n" + + " \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_01\",\r\n" + + " \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_02\",\r\n" + + " \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_03\"\r\n" + + " ],\r\n" + + " \"createdAt\": \"2021-05-20T09:32:08.809Z\",\r\n" + + " \"modifiedAt\": \"2021-05-20T09:52:04.255Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld2\"\r\n" + + " ]\r\n" + + " },\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_01\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"date\": \"2015-01-01\",\r\n" + + " \"description\": \"AĂƒÂ±o nuevo\",\r\n" + + " \"workingDay\": 0,\r\n" + + " \"schoolDay\": 0,\r\n" + + " \"publicHoliday\": 3,\r\n" + + " \"weekDay\": 4,\r\n" + + " \"createdAt\": \"2021-05-24T13:26:41.828Z\",\r\n" + + " \"modifiedAt\": \"2021-05-25T08:49:11.841Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " },\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_02\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"date\": \"2015-01-02\",\r\n" + + " \"description\": \"\",\r\n" + + " \"workingDay\": 1,\r\n" + + " \"schoolDay\": 1,\r\n" + + " \"publicHoliday\": 0,\r\n" + + " \"weekDay\": 5,\r\n" + + " \"createdAt\": \"2021-05-25T08:26:22.811Z\",\r\n" + + " \"modifiedAt\": \"2021-05-25T08:49:11.946Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " },\r\n" + + " {\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_03\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"date\": \"2015-01-03\",\r\n" + + " \"description\": \"\",\r\n" + + " \"workingDay\": 1,\r\n" + + " \"schoolDay\": 0,\r\n" + + " \"publicHoliday\": 0,\r\n" + + " \"weekDay\": 6,\r\n" + + " \"createdAt\": \"2021-05-25T08:26:22.846Z\",\r\n" + + " \"modifiedAt\": \"2021-05-25T08:49:12.047Z\",\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("Calendar::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + + return res; + } + + private List<JSONObject> getDaysOfCalendar(MongoDatabase database, City city, List<String> ids){ + + List<JSONObject> ret = new ArrayList<JSONObject>(); + + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + BasicDBObject inQuery = new BasicDBObject(); + inQuery.put("_id", new BasicDBObject("$in", ids)); + + FindIterable<Document> cursor = coll.find(inQuery); + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + try { + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + ret.add(jElem); + } catch (JSONException e) { + logger.error("DaySpecification::getDataID(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } catch (Exception e) { + logger.error("DaySpecification::getDataID(" + city + "):: Error: " + e.getMessage()); + } + } + + return ret; + + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "Calendar",Calendar.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "Calendar",Calendar.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collectionName = (TimeDataModel.CALENDAR + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collectionName, "Calendar"); + + + } + +} + diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CensusObservedController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CensusObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..13f1bcf755ae101a338d07d278ad2b80934540f0 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/CensusObservedController.java @@ -0,0 +1,524 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Population.CensusObserved; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class CensusObservedController extends GenericController implements IGenericController{ + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(CensusObservedController.class); + + public CensusObservedController () { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("CensusObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED+ "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> censColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + censColls.add(collectionName); + } + + for (int i = 0; i < arrData.length(); i++) { + CensusObserved record = CensusObserved.createCensusObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("CensusObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (censColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + doc.append("dateObserved", dateObs); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("CensusObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("CensusObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("CensusObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Census Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Census Observation' objects)"); + } + + logger.debug("CensusObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, + List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("CensusObserved::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(CensusObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(CensusObserved.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + //get the different collections between dates + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + List<String> censColls = new ArrayList<String>(); + + censColls = mongoDB.getModelCollectionNamesBetweenYears(collNamePrefix, startDate, endDate, SortingMode.DESC); + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("CensusObserved::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + MongoDatabase database = mongoDB.getDatabase(); + for (String collectionName: censColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("CensusObserved::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.censusObserved + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.censusObserved + "' data model's fields."); + } + logger.debug("CensusObserved::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("CensusObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + CensusObserved record = CensusObserved.createCensusObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year =Utils.getDateYear(dateObs); + String collectionName = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Census Observation' object)"); + } + + logger.debug("CensusObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "CensusObserved"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "CensusObserved", CensusObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:CensusObserved:amsterdam:2016-11-30T07:00:00.00Z\",\r\n" + + " \"type\": \"CensusObserved\",\r\n" + + " \"dateObserved\": \"2021-11-11T07:00:00.00Z\",\r\n" + + " \"createdAt\": \"2021-12-01T13:39:10.00Z\",\r\n" + + " \"modifiedAt\": \"2021-12-01T13:39:10.00Z\",\r\n" + + " \"source\": \"https://ec.europa.eu/eurostat/\",\r\n" + + " \"location\": {\r\n" + + " \"type\": \"Point\",\r\n" + + " \"coordinates\": [\r\n" + + " -4.754444444,\r\n" + + " 41.640833333\r\n" + + " ]\r\n" + + " },\r\n" + + " \"householdID\": 1,\r\n" + + " \"addressRegion\": \"Vienna\",\r\n" + + " \"householdCSWeight\": 0.0,\r\n" + + " \"personalCSWeight\": 0.0,\r\n" + + " \"gender\": \"male\",\r\n" + + " \"currentEconomicStatus\": 1,\r\n" + + " \"nationality\": \"AT\",\r\n" + + " \"employeeCashIncome\": 0.0,\r\n" + + " \"selfEmploymentLosses\": 0.0,\r\n" + + " \"unemploymentBenefits\": 0.0,\r\n" + + " \"oldAgeBenefits\": 0.0,\r\n" + + " \"survivorBenefits\": 0.0,\r\n" + + " \"sicknessBenefits\": 0.0,\r\n" + + " \"disabilityBenefits\": 0.0,\r\n" + + " \"educationAllowances\": 0.0, \r\n" + + " \"hsize\": 2,\r\n" + + " \"age\": 56,\r\n" + + " \"netIncome\": 16999.29,\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org//context.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/census-ngsi.jsonld\"\r\n" + + " ]\r\n" + + "}\r\n" + + ""; + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("CensusObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "CensusObserved",CensusObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "CensusObserved",CensusObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (PopulationDataModel.CENSUSOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "CensusObserved"); + + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/DaySpecificationController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/DaySpecificationController.java new file mode 100644 index 0000000000000000000000000000000000000000..e431e343c1ad1b8a2bf5cfde70518f918959a3ba --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/DaySpecificationController.java @@ -0,0 +1,515 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Population.CensusObserved; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.DaySpecification; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class DaySpecificationController extends GenericController implements IGenericController{ + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(DaySpecificationController.class); + + + public DaySpecificationController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("DaySpecification::insertData(" + city + ")::IN"); + + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + //check metadata + if (data.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data not found."); + } + else { + + try { + JSONArray jsArrData = new JSONArray(data); + + if (jsArrData.length() != 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + for (int i = 0; i < jsArrData.length(); i++) { + JSONObject jsData = jsArrData.getJSONObject(i); + //DaySpecification daySpec = new DaySpecification(); + //if (daySpec.createFromJSONLD(jsData)) { + DaySpecification daySpec = DaySpecification.createDaySpecification(jsData.toString()); + if (daySpec != null) { + if (daySpec.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", "Wrong input data, some required field(s) missing."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(daySpec)); + //need to replace "id" for "_id" + doc.put("_id", daySpec.getId()); + doc.remove("id"); + + //need to replace "context" for "@context" + doc.put("@context", daySpec.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", daySpec.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + coll.replaceOne(Filters.eq("_id", daySpec.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", daySpec.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", daySpec.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } else { + JSONObject o = new JSONObject(); + try { + o.put("id", jsData.getString("id")); + o.put("reason", "Wrong input data, some required field(s) missing."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + /* + JSONObject oIns = new JSONObject(); + JSONObject oNoIns = new JSONObject(); + JSONObject oUpd = new JSONObject(); + try { + oIns.put("inserted", arrInserted); + oNoIns.put("notInserted", arrNotInserted); + oUpd.put("updated", arrUpdated); + lstRes.add(oIns); + lstRes.add(oNoIns); + lstRes.add(oUpd); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + */ + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("DaySpecification::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is empty."); + } + + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + logger.debug("DaySpecification::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("DaySpecification::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(DaySpecification.class, filters)) { + //check the return fields + if (Utils.typeHasFields(DaySpecification.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) { + String strDateStart = dateFormat.format(startDate); + dateRange.put("$gte", strDateStart); + } + if (endDate != null) { + String strDateEnd= dateFormat.format(endDate); + dateRange.put("$lte", strDateEnd); + } + if (dateRange.isEmpty() == false) queryFilters.append("date", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("DaySpecification::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort= new BasicDBObject(); + //querySort.append("date", 1); + querySort.append("date", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and date + if (returnFields.contains("date") == false) returnFields.add("date"); + projection = Projections.fields(Projections.include(returnFields)); + } + + FindIterable<Document> cursor; + if (limit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(limit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("DaySpecification::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.daySpecification + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.daySpecification + "' data model's fields."); + } + + logger.debug("DaySpecification::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("DaySpecification::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + DaySpecification record = DaySpecification.createDaySpecification(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //need to put "id" + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + + try { + JSONObject jElem= new JSONObject(sDoc); + o.put("updatedData", jElem); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + logger.error("DaySpecification::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //no parseable --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Day Specification' object)"); + } + + logger.debug("DaySpecification::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + + return this.getDataByID(city, id, collNamePrefix, "DaySpecification"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "date", collectionName, "DaySpecification", DaySpecification.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example ="{\r\n" + + " \"id\": \"urn:ngsi-ld:DaySpecification:Bilbao:2015_01_01\",\r\n" + + " \"type\": \"DaySpecification\",\r\n" + + " \"createdAt\": \"2015-01-01T00:00:00Z\",\r\n" + + " \"modifiedAt\": \"2015-01-01T00:00:00Z\",\r\n" + + " \"date\": \"2015-01-01\",\r\n" + + " \"description\": \"Año nuevo\",\r\n" + + " \"workingDay\": 0,\r\n" + + " \"schoolDay\": 0,\r\n" + + " \"publicHoliday\": 3,\r\n" + + " \"weekDay\": 4,\r\n" + + " \"@context\": [\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/master/datamodels/calendar-ngsi.jsonld\"\r\n" + + " ]\r\n" + + " }"; + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("DaySpecification::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "DaySpecification",DaySpecification.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "DaySpecification",DaySpecification.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collectionName = (TimeDataModel.DAYSPECIFICATION + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collectionName, "DaySpecification"); + + } + +} + diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/ElectroMagneticObservedController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/ElectroMagneticObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..852ca75651bccc07b6d198aa8dadf6ab4c38e4f6 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/ElectroMagneticObservedController.java @@ -0,0 +1,426 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.ElectroMagneticObserved; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class ElectroMagneticObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(ElectroMagneticObservedController.class); + + public ElectroMagneticObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("ElectroMagneticObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED+ "_" + city + "_").toLowerCase(); + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + for (int i = 0; i < arrData.length(); i++) { + ElectroMagneticObserved record = ElectroMagneticObserved.createElectroMagneticObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObserved = Utils.ISO2Date(record.getDateObserved()); + + int year = Utils.getDateYear(dateObserved); + + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = collNamePrefix + year; + + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (airColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + + doc.append("dateObserved", dateObserved); + + Date dateObservedTo = Utils.ISO2Date(record.getDateObservedTo()); + if (dateObservedTo != null) + doc.append("dateObservedTo", dateObservedTo); + + Date dateObservedFrom = Utils.ISO2Date(record.getDateObservedFrom()); + if (dateObservedFrom != null) + doc.append("dateObservedFrom", dateObservedFrom); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + if (dateObsFrom != null) + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + if (dateObsTo != null) + doc.append("dateObservedTo", dateObsTo); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("ElectroMagneticObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'ElectroMagnetic Observed Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Electro Magnetic Observed' objects)"); + } + + logger.debug("ElectroMagneticObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "ElectroMagneticObserved", ElectroMagneticObserved.class); + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("ElectroMagneticObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + ElectroMagneticObserved record = ElectroMagneticObserved.createElectroMagneticObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = Utils.getDateYear(dateObs); + + String collectionName = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObserved()); + if (dateObsFrom != null) + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + if (dateObsTo != null) + doc.append("dateObservedTo", dateObsTo); + + //modification date is now + doc.append("dateModified", new Date()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("ElectroMagneticObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "ElectroMagneticObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "ElectroMagneticObserved", ElectroMagneticObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ \r\n" + + " \"id\": \"urn:ngsi-ld:ElectroMagneticObserved:ElectroMagneticObserved:MNCA-EM-018\", \r\n" + + " \"type\": \"ElectroMagneticObserved\", \r\n" + + " \"name\": \"MNCA-EM-018\", \r\n" + + " \"alternateName\": \"AirPort � global Observation\", \r\n" + + " \"description\": \"EMF observation\", \r\n" + + " \"location\": { \r\n" + + " \"type\": \"Point\", \r\n" + + " \"coordinates\": [ \r\n" + + " 43.664810, \r\n" + + " 7.196545 \r\n" + + " ] \r\n" + + " }, \r\n" + + " \"address\": { \r\n" + + " \"addressCountry\": \"FR\", \r\n" + + " \"addressLocality\": \"Nice\", \r\n" + + " \"streetAddress\": \"Terminal 2 - Parking 7\" \r\n" + + " }, \r\n" + + " \"areaServed\": \"Nice Aeroport\", \r\n" + + " \"refDevice\": \"urn:ngsi-ld:Device:NCE-T2-P7-EM03\", \r\n" + + " \"dateObserved\": \"2020-03-17T08:45:00Z\", \r\n" + + " \"eMF\": 950.12, \r\n" + + " \"observedAt\": \"2020-03-17TT08:45:00Z\", \r\n" + + " \"measurementType\": \"Instant\", \r\n" + + " \"measurementInterval\": 1, \r\n" + + " \"reliability\": 0.993 \r\n" + + "} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("ElectroMagneticObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "ElectroMagneticObserved",ElectroMagneticObserved.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "ElectroMagneticObserved",ElectroMagneticObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.ELECTROMAGNETICOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "ElectroMagneticObserved"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/EventController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/EventController.java new file mode 100644 index 0000000000000000000000000000000000000000..8e5c5ec07bf982e4c9fe98d6e55dcc578aa0223c --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/EventController.java @@ -0,0 +1,556 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Event.Event; +import com.tecnalia.urbanite.storage.DataModel.Event.EventDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.DaySpecification; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class EventController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(EventController.class); + + public EventController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + + @Override + public APIResponse insertData(City city, String data) { + logger.debug("Event::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> evtColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + evtColls.add(collectionName); + } + + for (int i = 0; i < arrData.length(); i++) { + Event record = Event.createEvent(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Event::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + Document doc = null; + try { + if (record.getStartDate() != null) { + Date startDate = Utils.ISO2Date(record.getStartDate()); + + int year = startDate.getYear() + 1900; + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (EventDataModel.EVENT + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "startDate" if the collection is new (not in previous collections read) + if (evtColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("startDate")); + } + } + } + else { + //no date specified --> generic collection + String collectionName = (EventDataModel.EVENT + "_" + city).toLowerCase(); + coll = database.getCollection(collectionName); + } + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + if (record.getStartDate() != null) doc.append("startDate", Utils.ISO2Date(record.getStartDate())); + if (record.getEndDate() != null) doc.append("endDate", Utils.ISO2Date(record.getEndDate())); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform dates (if present) + if (record.getStartDate() != null) doc.append("startDate", Utils.ISO2Date(record.getStartDate())); + if (record.getEndDate() != null) doc.append("endDate", Utils.ISO2Date(record.getEndDate())); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Event::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Event::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Event::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Event' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Event' objects)"); + } + + logger.debug("Event::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + logger.debug("Event::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(Event.class, filters)) { + //check the return fields + if (Utils.typeHasFields(Event.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + + int startYear = 0; + int endYear = 0; + if (startDate != null) startYear = startDate.getYear() + 1900; + if (endDate != null) endYear = endDate.getYear() + 1900; + + //get the different collections between dates + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> evtColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + + try { + int year = Integer.parseInt(collectionName.substring(collNamePrefix.length())); + //no errors --> check if it's in range + boolean addToList = true; + if (startYear > 0 && year < startYear) addToList = false; + if (endYear > 0 && year > endYear) addToList = false; + if (addToList) + evtColls.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR" format --> omit table + } + + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(evtColls); + else + Collections.sort(evtColls, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("startDate", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("Event::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("startDate", -1); //recent first + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and startDate + if (returnFields.contains("startDate") == false) returnFields.add("startDate"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: evtColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("Event::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + EventDataModel.EVENT + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + EventDataModel.EVENT + "' data model's fields."); + } + + logger.debug("Event::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("Event::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + Event record = Event.createEvent(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName; + if (record.getStartDate() != null) { + Date startDate = Utils.ISO2Date(record.getStartDate()); + int year = startDate.getYear() + 1900; + collectionName = (EventDataModel.EVENT + "_" + city + "_" + year).toLowerCase(); + } + else { + //no date specified --> generic collection + collectionName = (EventDataModel.EVENT + "_" + city).toLowerCase(); + } + + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + if (record.getStartDate() != null) doc.append("startDate", Utils.ISO2Date(record.getStartDate())); + if (record.getEndDate() != null) doc.append("endDate", Utils.ISO2Date(record.getEndDate())); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Event' object)"); + } + + logger.debug("Event::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "Event"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "Event", Event.class); + + } + + @Override + public JSONObject getExample() { +JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:event:bilbao:football:270720212355\",\r\n" + + " \"type\": \"Event\",\r\n" + + " \"startDate\": \"2021-10-15T16:30:00\",\r\n" + + " \"endDate\": \"2021-10-15T18:15:00\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " 43.264205, \r\n" + + " -2.949369\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"category\": \"football_match\",\r\n" + + " \"championship\": \"LaLiga\",\r\n" + + " \"homeTeam\": \"Athletic Bilbao\",\r\n" + + " \"guestTeam\": \"Valencia C.F.\", \r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org//context.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/event-ngsi.jsonld\"\r\n" + + " ]\r\n" + + "}\r\n" + + ""; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("Event::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "Event",Event.class); + + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "Event",Event.class); + + } + + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (EventDataModel.EVENT + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "Event"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GenericController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GenericController.java new file mode 100644 index 0000000000000000000000000000000000000000..6ed0f18fcda6355c00082e74ff9d4d50232fa7d1 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GenericController.java @@ -0,0 +1,551 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonMode; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.client.AggregateIterable; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DB.MongoDBUtils; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class GenericController { + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(GenericController.class); + + public GenericController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + public APIResponse getTDataRangeForDatasetWithYearInNameAtEnd(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort,String dateSortAndRangeField ,String collNamePrefix, String model,Class<?> cls) { + + logger.debug(model+"::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(cls, filters)) { + //check the return fields + if (Utils.typeHasFields(cls, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNamesBetweenYears(collNamePrefix, startDate, endDate, SortingMode.DESC); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + MongoDBUtils.addDateRangeFilter(queryFilters, startDate, endDate, dateSortAndRangeField); + + //fields + MongoDBUtils.addDateJSonFilter(queryFilters, filters); + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + + querySort.append(dateSortAndRangeField, sort.getOrder()); + + //return fields + Bson projection = MongoDBUtils.addReturnFields(returnFields,dateSortAndRangeField); + + int currentLimit = limit; + //search the element in collections + MongoDatabase database = mongoDB.getDatabase(); + for (String collectionName: airColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error(model+"::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + model + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + model + "' data model's fields."); + } + logger.debug(model+"::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort,String sortField ,String collNamePrefix, String model,Class<?> cls) { + logger.debug(model+"::getTData(" + city + ")::IN - Request for data with filtes [" + filters + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + if (Utils.typeHasFieldsJSON(cls, filters)) { + //check the return fields + if (Utils.typeHasFields(cls, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + //Filters + + BasicDBObject queryFilters = new BasicDBObject(); + MongoDBUtils.addDateJSonFilter(queryFilters, filters); + + //Sort + BasicDBObject querySort= new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append(sortField, sort.getOrder()); + + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,sort); + + + //return fields + Bson projection = MongoDBUtils.addReturnFields(returnFields,sortField); + + int currentLimit = limit; + //search the element in collections + for (String collectionName: airColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error(model+"::getTData(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + model + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + model+ "' data model's fields."); + } + logger.debug(model+"::getTData(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse getDataByID(City city, String id,String collNamePrefix, String model) { + + logger.debug(model+"::getDataID(" + city + ")::IN: Request for data with id = " + id); + APIResponse res = new APIResponse(); + + if (id.isEmpty() == false) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + boolean bFound = false; + + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC); + + //search the element in collections + for (String collectionName: airColls) { + MongoCollection<Document> coll = database.getCollection(collectionName); + Document doc = coll.find(new BasicDBObject("_id", id)).first(); + if (doc != null) { + try { + //need to replace "_id" field to "id" + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + JSONObject oDoc = new JSONObject(sDoc); + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + lstRes.add(oDoc); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + bFound = true; + break; + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + } + if (bFound == false) + { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Empty ID."); + } + + logger.debug(model+"::getDataID(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + public APIResponse getDistinct(City city, String[] fields,String collNamePrefix, String model,Class<?> cls) { + logger.debug(model+"::getDistinct(" + city + ")::IN - Request for distinct values of '" + fields + "'"); + + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + try { + if (Utils.typeHasFields(cls, new ArrayList<>(Arrays.asList(fields)))) { + + List<String> trafficCols = new ArrayList<String>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + //get the collection names + trafficCols = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC); + + ArrayList<HashMap<String,Object>> vl = new ArrayList<HashMap<String, Object>>(); + Document fieldDocs = new Document(); + for(String field:fields) { + fieldDocs.append(field, "$"+field); + } + //search the element in collections + for (String collectionName: trafficCols) { + MongoCollection<Document> coll = database.getCollection(collectionName); + try { + + AggregateIterable<Document> docs = coll.aggregate(Arrays.asList( + new Document("$group", new Document("_id", fieldDocs + )))); + + + MongoCursor<?> results = docs.iterator(); + while(results.hasNext()) { + Document doc =(Document) results.next(); + Document docid = (Document) doc.get("_id"); + Set<String> keys = docid.keySet(); + + HashMap<String,Object> map = new HashMap<String, Object>(); + for (String key:keys) { + if (docid.get(key) instanceof Document) { + + map.put(key,new JSONObject(((Document)docid.get(key)).toJson(JsonWriterSettings + .builder() + .outputMode(JsonMode.RELAXED) + .build()))); + } + else { + map.put(key, docid.get(key).toString()); + } + } + + vl.add(map); + + } + } catch (MongoException me) { + logger.error("Can't get disctint values: " + me.getMessage()); + } + } + mongoDB.close(); + try { + JSONObject jsDistinct = new JSONObject(); + jsDistinct.put("values", vl); + retData.add(jsDistinct); + res.setStatus(HttpStatus.OK); + res.setData(retData); + } catch (JSONException e) { + logger.error(model+"::getDistinct:: Error creating JSON with values: " + e.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Error creating JSON with values."); + } + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong field + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields, please check '" + model + "' data model's fields."); + } + + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + logger.debug(model+"::getDistinct(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse getDistinct(City city, String field,String collNamePrefix, String model,Class<?> cls) { + + logger.debug(model+"::getDistinct(" + city + ")::IN - Request for distinct vaules of '" + field + "'"); + + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check the field + if (Utils.typeHasFields(cls, new ArrayList<>(Arrays.asList(field)))) { + + List<String> airColls = new ArrayList<String>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + List<String> lsValues = new ArrayList<String>(); + + //search the element in collections + for (String collectionName: airColls) { + MongoCollection<Document> coll = database.getCollection(collectionName); + try { + DistinctIterable<?> docs = coll.distinct(field, BsonValue.class); + MongoCursor<?> results = docs.iterator(); + while(results.hasNext()) { + Object objRes = Utils.BsonValue2JavaType((BsonValue) results.next()); + String sValue = ""; + if (objRes.getClass() == Date.class) + sValue = Utils.Date2ISO((Date) objRes); + else + sValue = objRes.toString(); + if (lsValues.contains(sValue) == false) lsValues.add(sValue); + } + } catch (MongoException me) { + logger.error("Can't get disctint values: " + me.getMessage()); + } + } + + mongoDB.close(); + + try { + JSONObject jsDistinct = new JSONObject(); + jsDistinct.put("values", lsValues); + retData.add(jsDistinct); + res.setStatus(HttpStatus.OK); + res.setData(retData); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::getDistinct:: Error creating JSON with values: " + e.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Error creating JSON with values."); + } + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong field + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong field '" + field + "', please check '" +model + "' data model's fields."); + } + + logger.debug(model+"::getDistinct(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + public APIResponse deleteDataByID(City city, String id,String collNamePrefix, String model) { + + logger.debug(model+"::deleteDataByID(" + city + ")::IN: Request for data with id = " + id); + APIResponse res = new APIResponse(); + + if (id.isEmpty() == false) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + boolean bFound = false; + List<String> airColls = new ArrayList<String>(); + + //get names of collections, filter by model, and order in "descending" order (recent first) + //String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix, SortingMode.DESC); + + //search the element in collections + for (String collectionName: airColls) { + MongoCollection<Document> coll = database.getCollection(collectionName); + Document doc = coll.find(new BasicDBObject("_id", id)).first(); + if (doc != null) { + try { + DeleteResult result = coll.deleteOne(new Document("_id", id)); + if (result.getDeletedCount() != 0) { + //deleted + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("deleted", id); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + bFound = true; + break; + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + } + if (bFound == false) + { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Empty ID."); + } + + logger.debug(model+"::deleteDataByID(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GtfsShapeController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GtfsShapeController.java new file mode 100644 index 0000000000000000000000000000000000000000..e1340eb17618efd1d57efa95d8b6ddbb416ed37c --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/GtfsShapeController.java @@ -0,0 +1,489 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShape; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShapedataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class GtfsShapeController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(GtfsShapeController.class); + + public GtfsShapeController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("GtfsShape::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (GtfsShapedataModel.GTFSSHAPE+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + GtfsShape record = GtfsShape.createGtfsShape(arrData.getString(i)); + if (record == null || record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("GtfsShape::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + Date dateObs = null; + if (record.getDateObserved() != null && !record.getDateObserved().isEmpty() && record.getDateObserved().compareToIgnoreCase("null") != 0) { + dateObs= Utils.ISO2Date(record.getDateObserved()); + } + + else { + dateObs = d; + } + doc.append("dateObserved", dateObs); + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + Date dateObs = null; + if (record.getDateObserved() != null && !record.getDateObserved().isEmpty() && record.getDateObserved().compareToIgnoreCase("null") != 0) { + dateObs= Utils.ISO2Date(record.getDateObserved()); + } + + else { + dateObs = new Date(); + } + doc.append("dateObserved", dateObs); + + + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("GtfsShape::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("GtfsShape::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("GtfsShape::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + logger.debug("GtfsShape::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("GtfsShape::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(GtfsShape.class, filters)) { + //check the return fields + if (Utils.typeHasFields(GtfsShape.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + MongoCollection<Document> coll = null; + String CollectionName = (GtfsShapedataModel.GTFSSHAPE+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("GtfsShape::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateModified", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("GtfsShape::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.gtfsShape + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.gtfsShape + "' data model's fields."); + } + logger.debug("GtfsShape::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("GtfsShape::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + GtfsShape record = GtfsShape.createGtfsShape(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + Date dateObs = null; + if (record.getDateObserved() != null && !record.getDateObserved().isEmpty() && record.getDateObserved().compareToIgnoreCase("null") != 0) { + dateObs= Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + } + + else { + doc.append("dateObserved", existing.get("dateCreated")); + } + + + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('GtfsShape' object)"); + } + + logger.debug("GtfsShape::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "GtfsShape"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "GtfsShape", GtfsShape.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"id\": \"urn:ngsi-ld:PointOfInterest:\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -3.712247222222222,\r\n" + + " 40.423852777777775\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"source\": \"http://datos.madrid.es\",\r\n" + + " \"type\": \"GtfsShape\"\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("GtfsShape::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "GtfsShape",GtfsShape.class); + + + } + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "GtfsShape",GtfsShape.class); + + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (GtfsShapedataModel.GTFSSHAPE + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "GtfsShape"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IAggregatorController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IAggregatorController.java new file mode 100644 index 0000000000000000000000000000000000000000..c51575ec51f65ad883b90ae413c7a057fcfad7db --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IAggregatorController.java @@ -0,0 +1,30 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.Response; +import org.springframework.stereotype.Controller; + +import java.util.Date; +import java.util.Map; + +@Controller +public interface IAggregatorController +{ + Response aggregate(City city, String metric, Date start, Date end, AggregatorEnum aggregator, String downsample, Map<String,String> tags); +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IGenericController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IGenericController.java new file mode 100644 index 0000000000000000000000000000000000000000..f4a7f934f64036e3cbb8c475ea1709959cbc9f1d --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/IGenericController.java @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.Date; +import java.util.List; + +import org.codehaus.jettison.json.JSONObject; + +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; + +public interface IGenericController { + + public APIResponse insertData(City city, String data); + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort); + public APIResponse updateData(City city, String id, String data); + public APIResponse getDataByID(City city, String id); + public APIResponse getTData (City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort); + public JSONObject getExample(); + public APIResponse getDistinct(City city, String field); + public APIResponse getDistinct(City city, String[] fields); + public APIResponse deleteDataByID(City city, String id); + + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MapLayerController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MapLayerController.java new file mode 100644 index 0000000000000000000000000000000000000000..7201163243416c8dcfaece4adc6fa3be03715f6a --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MapLayerController.java @@ -0,0 +1,459 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Map.MapLayer; +import com.tecnalia.urbanite.storage.DataModel.Map.MapLayerDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class MapLayerController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(MapLayerController.class); + + public MapLayerController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("MapLayer::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + MongoCollection<Document> coll = null; + + String CollectionName = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + MapLayer record = MapLayer.createMapLayer(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("MapLayer::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + doc.remove("map"); + DBObject json = (DBObject)BasicDBObject.parse(record.getMap().toString());//(BasicDBObject) JSON.parse(record.getMap().toString()); + doc.put("map",json); + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("MapLayer::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("MapLayer::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("MapLayer::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'MapLayer Observed ' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'MapLayer' objects)"); + } + + logger.debug("MapLayer::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + logger.debug("MapLayer::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(MapLayer.class, filters)) { + //check the return fields + if (Utils.typeHasFields(MapLayer.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + coll = database.getCollection(CollectionName); + + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateModified", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("MapLayer::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + + querySort.append("dateCreated", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + } catch (JSONException e) { + logger.error("TransportStation::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.transportStation + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.transportStation + "' data model's fields."); + } + + logger.debug("MapLayer::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("MapLayer::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + MapLayer record = MapLayer.createMapLayer(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + + String collectionName = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("MapLayer::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "MapLayer"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city ).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateCreated", collNamePrefix, "MapLayer", MapLayer.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("MapLayer::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "MapLayer",MapLayer.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "MapLayer",MapLayer.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (MapLayerDataModel.MAPLAYER + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "MapLayer"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MetadataController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MetadataController.java new file mode 100644 index 0000000000000000000000000000000000000000..76a1c16706086a35d2fbf2b0f1299801148467f2 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/MetadataController.java @@ -0,0 +1,547 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.Common.CommonDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class MetadataController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(MetadataController.class); + + public MetadataController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + public APIResponse insertMetadata(String id, String metadata ) { + + logger.debug("Metadata::insertMetadata(" + id + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + //check metadata + if (metadata.isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input metadata not found."); + } + else { + try { + JSONObject jsMeta= new JSONObject(metadata); + //metadata in JSON format + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document doc = null; + try { + doc = Document.parse(jsMeta.toString()); + doc.put("_id", id); + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", id); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + String sExisting = existing.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + + //we have to pre-process some fields, to avoid duplicates: accessRights, foaf:Organization, modified, description and title + //1. Store the values of the new metadata, and delete them + String sMetaAccess = ""; + String sMetaModif = ""; + String sMetaOrgName = ""; + String sMetaOrgHomepage = ""; + + JSONArray jsArrMetaDesc = new JSONArray(); + JSONArray jsArrMetaTitle = new JSONArray(); + JSONArray jsArrNewMetaGraph = new JSONArray(); + + JSONArray jsArrMetaGraph = jsMeta.getJSONArray("@graph"); + for (int i = 0; i < jsArrMetaGraph.length(); i++) { + boolean bAddToGraph = true; + JSONObject o = jsArrMetaGraph.getJSONObject(i); + if (o.has("@type")) { + String type = o.getString("@type"); + //accessRights + if (type.compareTo("accessRights") == 0) { + if (o.has("label")) sMetaAccess = o.getString("label"); + bAddToGraph = false; + } + //foaf:Organization + else if (type.compareTo("foaf:Organization") == 0) { + if (o.has("name")) sMetaOrgName = o.getString("name"); + if (o.has("homepage")) sMetaOrgHomepage= o.getString("homepage"); + bAddToGraph = false; + } + //Dataset + else if (type.compareTo("dcat:Dataset") == 0) { + //modified field --> store and remove + if (o.has("modified")) { + sMetaModif = o.getString("modified"); + o.remove("modified"); + } + //description field --> store as an Array and remove + if (o.has("description")) { + Object objDesc = o.get("description"); + if (objDesc instanceof JSONArray) jsArrMetaDesc = o.getJSONArray("description"); + else if (objDesc instanceof JSONObject) jsArrMetaDesc.put(o.getJSONObject("description")); + o.remove("description"); + } + //title field --> store as an Array and remove + if (o.has("title")) { + Object objTitle = o.get("title"); + if (objTitle instanceof JSONArray) jsArrMetaTitle = o.getJSONArray("title"); + else if (objTitle instanceof JSONObject) jsArrMetaTitle.put(o.getJSONObject("title")); + o.remove("title"); + } + //remove other fields that will be updated: accessRights and publisher + if (o.has("accessRights")) o.remove("accessRights"); + if (o.has("publisher")) o.remove("publisher"); + } + } + if (bAddToGraph) jsArrNewMetaGraph.put(o); + } + + //set the new @graph + jsMeta.remove("@graph"); + jsMeta.put("@graph", jsArrNewMetaGraph); + + + //2. Update the previous existing metadata + JSONObject jsExisting = new JSONObject(sExisting); + JSONArray jsArrNewExistingGraph = new JSONArray(); + + JSONArray jsArrExistingGraph = jsExisting.getJSONArray("@graph"); + for (int i = 0; i < jsArrExistingGraph.length(); i++) { + JSONObject o = jsArrExistingGraph.getJSONObject(i); + if (o.has("@type")) { + String type = o.getString("@type"); + //accessRights + if (type.compareTo("accessRights") == 0) { + if (o.has("label")) { + if (sMetaAccess.isEmpty() == false) + o.put("label", sMetaAccess); + } + } + //foaf:Organization + else if (type.compareTo("foaf:Organization") == 0) { + if (o.has("name")) + if (sMetaOrgName.isEmpty() == false) + o.put("name", sMetaOrgName); + if (o.has("homepage")) + if (sMetaOrgHomepage.isEmpty() == false) + o.put("homepage", sMetaOrgHomepage); + } + //Dataset + else if (type.compareTo("dcat:Dataset") == 0) { + //modified + if (sMetaModif.isEmpty() == false) + o.put("modified", sMetaModif); + //description: the existing values must be updated (if changed) and the new values, added (if any) + if (o.has("description")) { + JSONArray jsArrExistingDesc = new JSONArray(); + Object objDesc = o.get("description"); + if (objDesc instanceof JSONArray) jsArrExistingDesc = o.getJSONArray("description"); + else if (objDesc instanceof JSONObject) jsArrExistingDesc.put(o.getJSONObject("description")); + JSONArray jsArrNewDesc = JoinJSONArraysByField(jsArrExistingDesc, jsArrMetaDesc, "@language"); + if (jsArrNewDesc.length() == 1) + o.put("description", jsArrNewDesc.get(0)); + else + o.put("description", jsArrNewDesc); + } + //title: the existing values must be updated (if changed) and the new values, added (if any) + if (o.has("title")) { + JSONArray jsArrExistingTitle = new JSONArray(); + Object objTitle = o.get("title"); + if (objTitle instanceof JSONArray) jsArrExistingTitle = o.getJSONArray("title"); + else if (objTitle instanceof JSONObject) jsArrExistingTitle.put(o.getJSONObject("title")); + JSONArray jsArrNewTitle = JoinJSONArraysByField(jsArrExistingTitle, jsArrMetaTitle, "@language"); + if (jsArrNewTitle.length() == 1) + o.put("title", jsArrNewTitle.get(0)); + else + o.put("title", jsArrNewTitle); + } + } + } + + jsArrNewExistingGraph.put(o); + } + + //set the new @graph + jsExisting.remove("@graph"); + jsExisting.put("@graph", jsArrNewExistingGraph); + + + //3. Add the two models + String finalMetadata = Utils.addJsonLdModels(jsExisting.toString(), jsMeta.toString()); + + try { + Document finalDoc = Document.parse(finalMetadata); + coll.replaceOne(Filters.eq("_id", id),finalDoc); + JSONObject o = new JSONObject(); + o.put("id", id); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", id); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Metadata::insertMetadata(" + id + "). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", id); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Metadata::insertMetadata(" + id + "). Error creating JSON: " + e.getMessage()); + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Metadata::insertMetadata(" + id + "). Error creating final response JSON: " + e.getMessage()); + } + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + catch (JSONException ex) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input metadata is not in JSON format"); + } + } + + logger.debug("Metadata::insertMetadata(" + id + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + public APIResponse getDataset(String id) { + + logger.debug("Metadata::getDataset(" + id + ")::IN"); + APIResponse res = new APIResponse(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document doc = coll.find(new BasicDBObject("_id", id)).first(); + if (doc != null) { + try { + doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + JSONObject oDoc = new JSONObject(sDoc); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + lstRes.add(oDoc); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else + { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::getDataset(" + id + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + public APIResponse deleteMetadata(String id) { + + logger.debug("Metadata::deleteMetadata(" + id + ")::IN"); + APIResponse res = new APIResponse(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + DeleteResult result = coll.deleteOne(new Document("_id", id)); + if (result.getDeletedCount() == 0) { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Dataset '" + id + "' not found."); + } else { + //deleted + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("deleted", id); + lstRes.add(o); + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::deleteMetadata(" + id + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + public APIResponse getCatalogueDatasets() { + logger.debug("Metadata::getCatalogueDatasets::IN"); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + //Sort? + //BasicDBObject querySort= new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + + //FindIterable<Document> cursor = coll.find().sort(querySort); + FindIterable<Document> cursor = coll.find(); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + String id = doc.get("_id").toString(); + doc.remove("_id"); + String sMeta = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + try { + JSONObject jMeta = new JSONObject(); + jMeta.put("id", id); + jMeta.put("metadata", new JSONObject(sMeta)); + retData.add(jMeta); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::getCatalogueDatasets::OUT [" + res.getStatus() + "]"); + return res; + } + + + //search example (mongodb): + //1. "text" index creation: + //db.getCollection('metadata').createIndex( { "$**": "text" } ) + //2. search for a value in any text field + //db.getCollection('metadata').find({$text: {$search: "Bilbao"}}) + + public APIResponse searchDatasets(String tags) { + logger.debug("Metadata::searchDatasets::IN"); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (CommonDataModel.METADATA + "").toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + + /* we'll search each value of the array in fields title, description and keywords --> OR OPERATION + * all of them must appear --> AND OPERATION + * i.e: search for "a" and "b" --> + * [title = "a" OR description = "a" OR keywords = "a"] AND [title = "b" OR description = "b" OR keywords = "b"] + * NOTE: we have several title and description fields: + * @graph.title.value (for datasets) + * @graph.title (for distributions) + * We need to search in all of them, because we can have somethin like: + * dataset "calendar bilbao" + * distribution "calendar bilbao 2016" + * If we search for 2016 only in dataset, it won't be foud, but it must + */ + BasicDBObject andQuery = new BasicDBObject(); + List<BasicDBObject> andLst = new ArrayList<BasicDBObject>(); + String[] tagList = tags.split(" "); + for (String tag: tagList) { + BasicDBObject orQuery = new BasicDBObject(); + List<BasicDBObject> orLst= new ArrayList<BasicDBObject>(); + //datasets: + BasicDBObject clauseTitleVal = new BasicDBObject("@graph.title.@value", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + BasicDBObject clauseDescVal = new BasicDBObject("@graph.description.@value", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + BasicDBObject clauseKeyword = new BasicDBObject("@graph.keyword", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + //distributions + BasicDBObject clauseTitle = new BasicDBObject("@graph.title", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + BasicDBObject clauseDesc = new BasicDBObject("@graph.description", new BasicDBObject("$regex", ".*" + tag + ".*").append("$options", "i")); + orLst.add(clauseTitle); + orLst.add(clauseDesc); + orLst.add(clauseTitleVal); + orLst.add(clauseDescVal); + orLst.add(clauseKeyword); + orQuery.put("$or", orLst); + andLst.add(orQuery); + } + andQuery.put("$and", andLst); + + + FindIterable<Document> cursor = coll.find(andQuery); + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + String id = doc.get("_id").toString(); + doc.remove("_id"); + String sMeta = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + try { + JSONObject jMeta = new JSONObject(); + jMeta.put("id", id); + jMeta.put("metadata", new JSONObject(sMeta)); + retData.add(jMeta); + } catch (JSONException e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + logger.debug("Metadata::searchDatasets::OUT [" + res.getStatus() + "]"); + return res; + } + + + private JSONArray JoinJSONArraysByField(JSONArray originalJSArray, JSONArray newJSArray, String fieldName) { + + Map<String, JSONObject> mpData = new HashMap<>(); + + try { + for (int origIndx = 0; origIndx < originalJSArray.length(); origIndx++) { + JSONObject obj = originalJSArray.getJSONObject(origIndx); + if (obj.has(fieldName)) + mpData.put(obj.getString(fieldName), obj); + } + + for (int newIndx = 0; newIndx < newJSArray.length(); newIndx++) { + JSONObject obj = newJSArray.getJSONObject(newIndx); + if (obj.has(fieldName)) + mpData.put(obj.getString(fieldName), obj); + } + + JSONArray jsRet = new JSONArray(); + for (String key : mpData.keySet()) + jsRet.put(mpData.get(key)); + + return jsRet; + } + catch (JSONException e) { + System.err.println ("Error Joinning JSONArrays by fields: " + e.getMessage()); + return originalJSArray; + } + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/NoiseLevelObservedController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/NoiseLevelObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..3fb117590378d639e4b5052736bd8ab1301d788e --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/NoiseLevelObservedController.java @@ -0,0 +1,408 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.DataModel.Environment.NoiseLevelObserved; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class NoiseLevelObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(NoiseLevelObservedController.class); + + public NoiseLevelObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("NoiseLevelObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + for (int i = 0; i < arrData.length(); i++) { + NoiseLevelObserved record = NoiseLevelObserved.createNoiseLevelObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObservedFrom = Utils.ISO2Date(record.getDateObservedFrom()); + + int year = Utils.getDateYear(dateObservedFrom); + + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = collNamePrefix + year; + + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (airColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObservedFrom")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + Date dateObservedTo = Utils.ISO2Date(record.getDateObservedTo()); + doc.append("dateObservedTo", dateObservedTo); + + doc.append("dateObservedFrom", dateObservedFrom); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + doc.append("dateObservedTo", dateObsTo); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Noise Level Observed Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Noise Level Observed' objects)"); + } + + logger.debug("NoiseLevelObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateObservedFrom", collNamePrefix, "NoiseLevelObserved", NoiseLevelObserved.class); + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("NoiseLevelObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + NoiseLevelObserved record = NoiseLevelObserved.createNoiseLevelObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + int year = Utils.getDateYear(dateObsFrom); + + String collectionName = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + + //Date dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + doc.append("dateObservedFrom", dateObsFrom); + + Date dateObsTo = Utils.ISO2Date(record.getDateObservedTo()); + doc.append("dateObservedTo", dateObsTo); + + //modification date is now + doc.append("dateModified", new Date()); + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("NoiseLevelObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "NoiseLevelObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObservedFrom", collNamePrefix, "NoiseLevelObserved", NoiseLevelObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ \r\n" + + " \"id\": \"Vitoria-NoiseLevelObserved-2016-12-28T11:00:00_2016-12-28T12:00:00\", \r\n" + + " \"type\": \"NoiseLevelObserved\", \r\n" + + " \"LAS\": 91.6, \r\n" + + " \"LAeq\": 67.8, \r\n" + + " \"LAeq_d\": 65.4, \r\n" + + " \"LAmax\": 94.5, \r\n" + + " \"dateObservedFrom\": \"2016-12-28T11:00:00.00Z\", \r\n" + + " \"dateObservedTo\": \"2016-12-28T12:00:00.00Z\", \r\n" + + " \"location\": { \r\n" + + " \"type\": \"Point\", \r\n" + + " \"coordinates\": [-2.698, 42.8491] \r\n" + + " } \r\n" + + "} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("NoiseLevelObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "NoiseLevelObserved",NoiseLevelObserved.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "NoiseLevelObserved",NoiseLevelObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (EnvironmentDataModel.NOISELEVELOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "NoiseLevelObserved"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/OriginDestinationMatrixController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/OriginDestinationMatrixController.java new file mode 100644 index 0000000000000000000000000000000000000000..ea65c5faf4987bdbe257871384b6ce419f1652b2 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/OriginDestinationMatrixController.java @@ -0,0 +1,556 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShape; +import com.tecnalia.urbanite.storage.DataModel.GtfsShape.GtfsShapedataModel; +import com.tecnalia.urbanite.storage.DataModel.Transportation.OriginDestinationMatrix; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class OriginDestinationMatrixController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(OriginDestinationMatrixController.class); + + public OriginDestinationMatrixController () { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("OriginDestinationMatrix::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String CollectionName = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + coll = database.getCollection(CollectionName); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> odCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(CollectionName)) + odCols.add(collectionName); + } + + + for (int i = 0; i < arrData.length(); i++) { + OriginDestinationMatrix record = OriginDestinationMatrix.createOriginDestinationMatrix(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("createdAt", d); + doc.append("modifiedAt", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Origin Destination Matrix' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Origin Destination Matrix' objects)"); + } + + logger.debug("OriginDestinationMatrix::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, + List<String> returnFields, int limit, SortingMode sort) { + logger.debug("OriginDestinationMatrix::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(OriginDestinationMatrix.class, filters)) { + //check the return fields + if (Utils.typeHasFields(OriginDestinationMatrix.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city).toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> odCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + odCols.add(collectionName.toLowerCase()); + break; + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(odCols); + else + Collections.sort(odCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + ////dates + //BasicDBObject dateRange = new BasicDBObject (); + //if (startDate != null) dateRange.put("$gte", startDate); + //if (endDate != null) dateRange.put("$lte", endDate); + //if (dateRange.isEmpty() == false) queryFilters.append("startDate", dateRange); + + //dates and hours + DateFormat dFDate = new SimpleDateFormat("YYYY-MM-dd"); + DateFormat dFHour = new SimpleDateFormat("HH:mm:ss"); + dFDate.setTimeZone(TimeZone.getTimeZone("UTC")); + dFHour.setTimeZone(TimeZone.getTimeZone("UTC")); + if (startDate != null) { + queryFilters.append("startDate", new BasicDBObject("$gte", dFDate.format(startDate))); + queryFilters.append("startPeriod", new BasicDBObject("$gte", dFHour.format(startDate))); + } + if (endDate != null) { + queryFilters.append("endDate", new BasicDBObject("$lte", dFDate.format(endDate))); + queryFilters.append("endPeriod", new BasicDBObject("$lte", dFHour.format(endDate))); + } + + + + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and startDate + if (returnFields.contains("startDate") == false) returnFields.add("startDate"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: odCols) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.originDestinationMatrix + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.originDestinationMatrix + "' data model's fields."); + } + + logger.debug("OriginDestinationMatrix::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("OriginDestinationMatrix::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + OriginDestinationMatrix record = OriginDestinationMatrix.createOriginDestinationMatrix(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + String collectionName = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("createdAt", existing.get("createdAt")); + //modification date is now + doc.append("modifiedAt", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //o.put("updatedData", doc); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse--> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Origin Destination Matrix' object)"); + } + + logger.debug("OriginDestinationMatrix::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "OriginDestinationMatrix"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "OriginDestinationMatrix", OriginDestinationMatrix.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:odm:bilbao:wifi:daily:270720212355\",\r\n" + + " \"aggregationType\": \"daily\",\r\n" + + " \"type\": \"OriginDestinationMatrix\",\r\n" + + " \"category\": \"wifi\",\r\n" + + " \"endDate\": \"2021-09-30\",\r\n" + + " \"endPeriod\": \"23:59:00\",\r\n" + + " \"matrixData\": [\r\n" + + " {\r\n" + + " \"arrivesTo\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"departsFrom\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"time\": 38,\r\n" + + " \"volume\": 1246.0,\r\n" + + " \"volumePercentage\": 3.15\r\n" + + " },\r\n" + + " {\r\n" + + " \"arrivesTo\": \"BASURTO\",\r\n" + + " \"departsFrom\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"time\": 33,\r\n" + + " \"volume\": 3581.0,\r\n" + + " \"volumePercentage\": 9.05\r\n" + + " },\r\n" + + " {\r\n" + + " \"arrivesTo\": \"CASCO VIEJO\",\r\n" + + " \"departsFrom\": \"ACCESOS V (ACCESOS VIARIOS)\",\r\n" + + " \"time\": 66,\r\n" + + " \"volume\": 899.0,\r\n" + + " \"volumePercentage\": 2.27\r\n" + + " }\r\n" + + " ],\r\n" + + " \"startDate\": \"2021-09-01\",\r\n" + + " \"startPeriod\": \"00:00:00\",\r\n" + + " \"travelMode\": \"all\",\r\n" + + " \"zones\": \"Bilbao_zones\",\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org//context.jsonld\",\r\n" + + " \"https://git.code.tecnalia.com/urbanite/public/-/raw/main/datamodels/ODMatrix-ngsi.jsonld\"\r\n" + + " ]\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("OriginDestinationMatrix::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "OriginDestinationMatrix",OriginDestinationMatrix.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "OriginDestinationMatrix",OriginDestinationMatrix.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (TransportationDataModel.ORIGINDESTINATIONMATRIX + "_" + city ).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "OriginDestinationMatrix"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PointOfInterestController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PointOfInterestController.java new file mode 100644 index 0000000000000000000000000000000000000000..e69e286123e26ecabd5e81fa874f0489b22fb28c --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PointOfInterestController.java @@ -0,0 +1,475 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Environment.EnvironmentDataModel; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterest; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterestDataModel; +import com.tecnalia.urbanite.storage.DataModel.Time.TimeDataModel; +import com.tecnalia.urbanite.storage.DataModel.Transportation.OriginDestinationMatrix; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class PointOfInterestController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(PointOfInterestController.class); + + public PointOfInterestController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("PointOfInterest::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (PointOfInterestDataModel.POINTOFINTEREST+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + PointOfInterest record = PointOfInterest.createPointOfInterest(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("PointOfInterest::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PointOfInterest' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PointOfInterest' objects)"); + } + + logger.debug("PointOfInterest::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("PointOfInterest::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(PointOfInterest.class, filters)) { + //check the return fields + if (Utils.typeHasFields(PointOfInterest.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (PointOfInterestDataModel.POINTOFINTEREST+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateModified", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("PointOfInterest::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateModified", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateModified") == false) returnFields.add("dateModified"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + } catch (JSONException e) { + logger.error("PointOfInterest::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.pointOfInterest + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.pointOfInterest + "' data model's fields."); + } + logger.debug("PointOfInterest::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("PointOfInterest::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + PointOfInterest record = PointOfInterest.createPointOfInterest(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('PointOfInterest' object)"); + } + + logger.debug("PointOfInterest::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "PointOfInterest"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "category", collNamePrefix, "PointOfInterest", PointOfInterest.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"address\": {\r\n" + + " \"addressCountry\": \"ES\",\r\n" + + " \"addressLocality\": \"Madrid\",\r\n" + + " \"streetAddress\": \"Plaza de Espa\\u00f1a\",\r\n" + + " \"type\": \"PostalAddress\"\r\n" + + " },\r\n" + + " \"areaServed\": \"Brooklands\",\r\n" + + " \"id\": \"urn:ngsi-ld:PointOfInterest:\",\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " -3.712247222222222,\r\n" + + " 40.423852777777775\r\n" + + " ],\r\n" + + " \"type\": \"Point\"\r\n" + + " },\r\n" + + " \"source\": \"http://datos.madrid.es\",\r\n" + + " \"type\": \"PointOfInterest\"\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("PointOfInterest::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "PointOfInterest",PointOfInterest.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "PointOfInterest",PointOfInterest.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (PointOfInterestDataModel.POINTOFINTEREST + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "PointOfInterest"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PopulationObservedController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PopulationObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..6f386bb488a06235670e1f2b3962248f69f620a3 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/PopulationObservedController.java @@ -0,0 +1,373 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.google.gson.FieldNamingPolicy; +import com.mongodb.MongoWriteException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationDataModel; +import com.tecnalia.urbanite.storage.DataModel.Population.PopulationObserved; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class PopulationObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(PopulationObservedController.class); + + public PopulationObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("PopulationObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + List<String> airColls = new ArrayList<String>(); + airColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + + for (int i = 0; i < arrData.length(); i++) { + PopulationObserved record = PopulationObserved.createPopulationObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + Date now = new Date(); + int year = Utils.getDateYear(now); + + + doc = Document.parse(Utils.Object2JSON(record,FieldNamingPolicy.LOWER_CASE_WITH_DASHES)); + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) { + doc.append("dateObserved", dateObs); + + year = Utils.getDateYear(dateObs); + } + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = collNamePrefix + year; + + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + } + + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + + doc.append("dateCreated", now); + doc.append("dateModified", now); + + coll.insertOne(doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("PopulationObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PopulationObserved Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'PopulationObserved' objects)"); + } + + logger.debug("PopulationObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTDataRangeForDatasetWithYearInNameAtEnd(city, startDate, endDate, filters, returnFields, limit, sort, "dateModified", collNamePrefix, "NoiseLevelObserved", PopulationObserved.class); + + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("PopulationObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + PopulationObserved record = PopulationObserved.createPopulationObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + Date now = new Date(); + int year = Utils.getDateYear(now); + + MongoDatabase database = mongoDB.getDatabase(); + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + if (dateObs != null) { + year = Utils.getDateYear(dateObs); + } + String collectionName = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + if (dateObs != null) + doc.append("dateObserved", dateObs); + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Noise Level Observed' object)"); + } + + logger.debug("PopulationObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "PopulationObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateModified", collNamePrefix, "PopulationObserved", PopulationObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ } "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("PopulationObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "PopulationObserved",PopulationObserved.class); + + } + + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "PopulationObserved",PopulationObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (PopulationDataModel.POPULATIONOBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "PopulationObserved"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TouristTripController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TouristTripController.java new file mode 100644 index 0000000000000000000000000000000000000000..7002c418babd346081caa850d628fcd212e6866f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TouristTripController.java @@ -0,0 +1,821 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterest; +import com.tecnalia.urbanite.storage.DataModel.PointOfInterest.PointOfInterestDataModel; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTrip; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTripDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class TouristTripController extends GenericController implements IGenericController { + + private Logger logger = LoggerFactory.getLogger(TouristTripController.class); + private DBConfiguration.DBParams dbParams; + + + private MongoDBManager mongoDBGlobal = null; + + + + public TouristTripController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + this.mongoDBGlobal = new MongoDBManager(dbParams); + this.mongoDBGlobal.connect(); + } + + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("TouristTrip::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> touristCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + touristCols.add(collectionName); + } + + + for (int i = 0; i < arrData.length(); i++) { + TouristTrip record = TouristTrip.createTouristTrip(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("TouristTrip::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getStartDate()); + int year = dateObs.getYear() + 1900; + int month = dateObs.getMonth() + 1; + String yearMonth = year + "_" + (month<10?"0":"") + month; + if (collectionList.containsKey(yearMonth)) + coll = collectionList.get(yearMonth); + else { + String collectionName = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_" + yearMonth).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(yearMonth, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (touristCols.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("startDate")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + + Date startDate = null; + if (record.getStartDate() != null) { + if (record.getStartDate().isEmpty() == false && record.getStartDate().compareToIgnoreCase("null") != 0) + startDate= Utils.ISO2Date(record.getStartDate()); + } + if (startDate != null) doc.append("startDate", startDate); + + Date endDate = null; + if (record.getEndDate() != null) { + if (record.getEndDate().isEmpty() == false && record.getEndDate().compareToIgnoreCase("null") != 0) + endDate= Utils.ISO2Date(record.getEndDate()); + } + if (endDate != null) doc.append("endDate", endDate); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + Date startDate = null; + if (record.getStartDate() != null) { + if (record.getStartDate().isEmpty() == false && record.getStartDate().compareToIgnoreCase("null") != 0) + startDate= Utils.ISO2Date(record.getStartDate()); + } + if (startDate != null) doc.append("startDate", startDate); + + + Date endDate = null; + if (record.getEndDate() != null) { + if (record.getEndDate().isEmpty() == false && record.getEndDate().compareToIgnoreCase("null") != 0) + endDate= Utils.ISO2Date(record.getEndDate()); + } + if (endDate != null) doc.append("endDate", endDate); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TouristTrip::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TouristTrip::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("TouristTrip::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TouristTrip' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TouristTrip' objects)"); + } + + logger.debug("TouristTrip::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + public APIResponse getTDataRangeOtherMethod(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TouristTrip::getTDataRangeOtherMethod(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TouristTrip.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TouristTrip.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRangeOtherMethod(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + + logger.info("TouristTrip::getTDataRangeOtherMethod::Antes del For"); + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + + List<Document> results = new ArrayList<>(); + cursor.into(results); + + allResults.addAll(results); + + int resSize = results.size(); + + //reached limit? + int total = allResults.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + + } + logger.info("TouristTrip::getTDataRangeOtherMethod::Despues del For"); + + logger.info("TouristTrip::getTDataRangeOtherMethod::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRangeOtherMethod(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TouristTrip::getTDataRangeOtherMethod::Despues de procesamiento"); + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.touristTrip + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.touristTrip + "' data model's fields."); + } + + + logger.debug("TouristTrip::getTDataRangeOtherMethod(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TouristTrip::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TouristTrip.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TouristTrip.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("startDate", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TouristTrip::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.touristTrip + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.touristTrip + "' data model's fields."); + } + + logger.debug("TouristTrip::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("TouristTrip::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + try { + TouristTrip record = TouristTrip.createTouristTrip(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getStartDate()); + int year = dateObs.getYear() + 1900; + int month = dateObs.getMonth() + 1; + String yearMonth = year + "_" + (month<10?"0":"") + month; + String collectionName = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_" + yearMonth).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + Date startDate = null; + if (record.getStartDate() != null) { + if (record.getStartDate().isEmpty() == false && record.getStartDate().compareToIgnoreCase("null") != 0) + startDate= Utils.ISO2Date(record.getEndDate()); + } + if (startDate != null) doc.append("startDate", startDate); + + + Date endDate = null; + if (record.getEndDate() != null) { + if (record.getEndDate().isEmpty() == false && record.getEndDate().compareToIgnoreCase("null") != 0) + endDate= Utils.ISO2Date(record.getEndDate()); + } + if (endDate != null) doc.append("endDate", endDate); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //o.put("updatedData", doc); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse--> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('TouristTrip' object)"); + } + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + logger.debug("TouristTrip::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "TouristTrip"); + + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "TouristTrip", TouristTrip.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("TouristTrip::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "TouristTrip",TouristTrip.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "TouristTrip",TouristTrip.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (TouristTripDataModel.TouristTrip + "_" + city + "_bikes_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "TouristTrip"); + + } + +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TrafficFlowObservedController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TrafficFlowObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..ae4d3e10a2d2c0d88c598319ffbe736fead3219f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TrafficFlowObservedController.java @@ -0,0 +1,1516 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import com.google.gson.JsonParser; +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.*; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.opentsdb.client.OpentsdbClient; +import com.tecnalia.opentsdb.client.PointBuilder; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTrip; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TrafficFlowObserved; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; +import com.tecnalia.urbanite.storage.controllers.models.DatabaseAction; +import com.tecnalia.urbanite.storage.controllers.models.enums.DatabaseActionTypeEnum; +import com.tecnalia.urbanite.storage.exception.DocumentNotFoundException; +import com.tecnalia.urbanite.storage.exception.MongoDbConnectionException; +import com.tecnalia.urbanite.storage.exception.OpenTsdbOperationException; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; + +@Component +public class TrafficFlowObservedController extends GenericController implements IGenericController { + + + private final Logger logger = LoggerFactory.getLogger(TrafficFlowObservedController.class); + private DBConfiguration.DBParams dbParams; + private MongoDBManager mongoDBGlobal = null; + + public TrafficFlowObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + this.mongoDBGlobal = new MongoDBManager(dbParams); + this.mongoDBGlobal.connect(); + } + + @Override + public APIResponse insertData(City city, String data) + { + logger.debug("TrafficFlowObserved::insertData(" + city + ")::IN"); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try + { + JSONArray dataArray = new JSONArray(data); + + if ( dataArray.length() <= 0 ) + { + logger.debug("TrafficFlowObserved::insertData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Input data is not in required format (list of 'Traffic Flow Observation' objects)"); + } + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + + if ( !mongoDB.connect()) + { + logger.debug("TrafficFlowObserved::insertData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR,"Can't connect to database."); + } + + MongoDatabase database = mongoDB.getDatabase(); + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + + for (String collectionName: colNames) + { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) + { + trafficCols.add(collectionName); + } + } + + for (int row = 0; row < dataArray.length(); row++) + { + save( TrafficFlowObserved.createTrafficFlowObserved(dataArray.getString(row)), + collectionList, trafficCols, database, city, arrInserted, arrUpdated, arrNotInserted); + } + + } catch (JSONException ex) + { + logger.error(ex.getMessage(), ex); + return new APIResponse(HttpStatus.BAD_REQUEST, "Input data is not in required format (list of 'Traffic Flow Observation' objects)"); + } + + logger.debug("TrafficFlowObserved::insertData(" + city + ")::OUT [" + HttpStatus.OK + "]: " + + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + + return new APIResponse(HttpStatus.OK, buildResponse(arrInserted, arrNotInserted, arrUpdated, city)); + } + + private void save(TrafficFlowObserved trafficFlowObserved, Map<String, MongoCollection<Document>> collectionList, + List<String> trafficCols, MongoDatabase database, City city, JSONArray arrInserted, JSONArray arrUpdated, JSONArray arrNotInserted) + { + if (trafficFlowObserved.isValid()) + { + DatabaseAction documentAction = saveDocument(trafficFlowObserved, collectionList, trafficCols, database,city); + DatabaseAction metricAction = saveMetric(trafficFlowObserved, city); + + if (documentAction.actionType == DatabaseActionTypeEnum.INSERTED && metricAction.actionType == DatabaseActionTypeEnum.INSERTED) + { + registry(trafficFlowObserved, city, arrInserted); + } + else if (documentAction.actionType == DatabaseActionTypeEnum.UPDATED || metricAction.actionType == DatabaseActionTypeEnum.UPDATED ) + { + registry(trafficFlowObserved, city, arrUpdated); + } + else if (documentAction.actionType == DatabaseActionTypeEnum.NOT_INSERTED ) + { + registry(trafficFlowObserved, city, arrNotInserted, documentAction.reason ); + } + else if (metricAction.actionType == DatabaseActionTypeEnum.NOT_INSERTED ) + { + registry(trafficFlowObserved, city, arrNotInserted, metricAction.reason ); + } + } + else + { + registry(trafficFlowObserved, city, arrNotInserted, "Wrong input data, missing some required field(s) or wrong values."); + } + } + + public APIResponse getTDataRangeOtherMethod(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Antes del For"); + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + + List<Document> results = new ArrayList<>(); + cursor.into(results); + + allResults.addAll(results); + + int resSize = results.size(); + + //reached limit? + int total = allResults.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Despues del For"); + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod::Despues de procesamiento"); + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + ex.getMessage()); + } + + return res; + } + + public APIResponse getTDataRangeOtherMethod2(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + List<String> trafficCols = new ArrayList<String>(); + +// MongoDBManager mongoDB = new MongoDBManager(dbParams); +// if (mongoDB.connect()) { + + MongoDatabase database = mongoDBGlobal.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } +// mongoDB.close(); +// } +// else { +// res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); +// res.setError("Cann't connect to database."); +// } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Antes del For"); + + for (String collectionName: trafficCols) { + +// if (mongoDB.connect()) { + +// MongoDatabase database = mongoDBGlobal.getDatabase(); + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + + List<Document> results = new ArrayList<>(); + cursor.into(results); + + allResults.addAll(results); + + int resSize = results.size(); + + //reached limit? + int total = allResults.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + +// mongoDB.close(); + +// } +// else { +// res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); +// res.setError("Cann't connect to database."); +// } + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Despues del For"); + + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TrafficFlowObserved::getTDataRangeOtherMethod2::Despues de procesamiento"); + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + + logger.debug("TrafficFlowObserved::getTDataRangeOtherMethod2(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + public APIResponse getTDataRangeThreadsMethod(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + List<Document> allResults = new ArrayList<Document>(); + + + + List<Thread> threads = new ArrayList<>(); + List<WorkerThread> workers = new ArrayList<>(); + + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + WorkerThread w = new WorkerThread(collectionName, cursor); + workers.add(w); + Thread t = new Thread(w); + threads.add(t); + t.start(); + } + + for(int i = 0; i < threads.size(); i++) { + try { + threads.get(i).join(); + } catch (InterruptedException ex) { + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Exception " + ex.getMessage()); + } + } + + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Antes de obtener resultados Threads"); + + for(int i = 0; i < threads.size(); i++) { + List<Document> results = workers.get(i).getResults(); + int numElems = results.size(); + if (numElems <= currentLimit) { + allResults.addAll(results); + currentLimit = currentLimit - numElems; + } else { + allResults.addAll(results.subList(0, currentLimit)); + break; + } + } + + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Despues de obtener resultados Threads"); + + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Antes de procesamiento"); + int resSize = allResults.size(); + for (int nElem = 0; nElem < resSize; nElem++) { + Document doc = allResults.get(nElem); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + + } + logger.info("TrafficFlowObserved::getTDataRangeThreadsMethod::Despues de procesamiento"); + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + + logger.debug("TrafficFlowObserved::getTDataRangeThreadsMethod(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TrafficFlowObserved::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TrafficFlowObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TrafficFlowObserved.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String startYearMonth = ""; + String endYearMonth = ""; + if (startDate != null) { + int year = startDate.getYear() + 1900; + int month = startDate.getMonth() + 1; + startYearMonth = year + "_" + (month<10?"0":"") + month; + } + if (endDate != null) { + int year = endDate.getYear() + 1900; + int month = endDate.getMonth() + 1; + endYearMonth = year + "_" + (month<10?"0":"") + month; + } + + + //get the different collections between dates + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> trafficCols = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + String dte = collectionName.substring(collNamePrefix.length()); + String[] dteParts = dte.split("_"); + if (dteParts.length == 2) { + try { + int year = Integer.parseInt(dteParts[0]); + int month = Integer.parseInt(dteParts[1]); + //no errors --> check if it's in range + boolean addToList = true; + if (startYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(startYearMonth) < 0) addToList = false; + } + if (endYearMonth.isEmpty() == false) { + if (dte.compareToIgnoreCase(endYearMonth) > 0) addToList = false; + } + if (addToList) + trafficCols.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR_MONTH" format --> omit table + } + } + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(trafficCols); + else + Collections.sort(trafficCols, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: trafficCols) { + + //String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.trafficFlowObserved + "' data model's fields."); + } + + logger.debug("TrafficFlowObserved::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + + APIResponse response = null; + + try + { + TrafficFlowObserved trafficFlowObserved = TrafficFlowObserved.createTrafficFlowObserved(data); + + if (trafficFlowObserved == null) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Input data is not in required format ('Traffic Flow Observation' object)"); + } + if (!trafficFlowObserved.isValid()) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Wrong input data, some required field(s) missing."); + } + if (trafficFlowObserved.getId().compareTo(id) != 0) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST,"Wrong input data, IDs are different."); + } + + List<JSONObject> documentResponse = this.updateDocument(trafficFlowObserved, city, id ); + HttpResponse metricResponse = this.updateMetric(trafficFlowObserved, city); + + if (metricResponse.getStatusLine().getStatusCode() == HttpStatus.OK.value()) + { + // Llegados hasta aquĂ es que ha ido bien tanto mongoDb como openTsdb por lo que devolvemos la respuesta mongodb para no tener que unificar una respuesta. + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.OK + "]"); + return new APIResponse(HttpStatus.OK, documentResponse); + } + else + { + // Si ha ido mal opentsdb, entonces devolvemos su error nativo. + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()) + "]"); + return new APIResponse(HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()), metricResponse.getStatusLine().getReasonPhrase()); + } + } + + catch (MongoDbConnectionException ex) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.INTERNAL_SERVER_ERROR + "]"); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Cann't connect to database."); + } + catch (DocumentNotFoundException ex) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.NOT_FOUND + "]"); + return new APIResponse(HttpStatus.NOT_FOUND, ex.getMessage()); + } + catch (Exception ex) + { + logger.debug("TrafficFlowObserved::updateData(" + city + ")::OUT [" + HttpStatus.INTERNAL_SERVER_ERROR + "]"); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR,"Server Error: " + ex.getMessage()); + } + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "TrafficFlowObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "TrafficFlowObserved", TrafficFlowObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\",\r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"\r\n" + + " ],\r\n" + + " \"address\": {\r\n" + + " \"addressCountry\": \"ES\",\r\n" + + " \"addressLocality\": \"Valladolid\",\r\n" + + " \"streetAddress\": \"Avenida de Salamanca\",\r\n" + + " \"type\": \"PostalAddress\"\r\n" + + " },\r\n" + + " \"averageHeadwayTime\": 0.5,\r\n" + + " \"averageVehicleLength\": 9.87,\r\n" + + " \"averageVehicleSpeed\": 52.6,\r\n" + + " \"dateObserved\": \"2016-12-07T11:10:00Z\",\r\n" + + " \"id\": \"urn:ngsi-ld:TrafficFlowObserved:TrafficFlowObserved-Valladolid-osm-60821110\",\r\n" + + " \"intensity\": 197,\r\n" + + " \"laneDirection\": \"forward\",\r\n" + + " \"laneId\": 1,\r\n" + + " \"location\": {\r\n" + + " \"coordinates\": [\r\n" + + " [\r\n" + + " -4.73735395519672,\r\n" + + " 41.6538181849672\r\n" + + " ],\r\n" + + " [\r\n" + + " -4.73414858659993,\r\n" + + " 41.6600594193478\r\n" + + " ],\r\n" + + " [\r\n" + + " -4.73447575302641,\r\n" + + " 41.659585195093\r\n" + + " ]\r\n" + + " ],\r\n" + + " \"type\": \"LineString\"\r\n" + + " },\r\n" + + " \"occupancy\": 0.76,\r\n" + + " \"reversedLane\": false,\r\n" + + " \"type\": \"TrafficFlowObserved\"\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("TrafficFlowObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + return this.getDistinct(city, fields, collNamePrefix, "TrafficFlowObserved",TrafficFlowObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city).toLowerCase(); + return this.getDistinct(city, field, collNamePrefix, "TrafficFlowObserved",TrafficFlowObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::IN: Request for data with id = " + id); + + try + { + if (id.isEmpty()) + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::OUT [" + HttpStatus.BAD_REQUEST + "]"); + return new APIResponse(HttpStatus.BAD_REQUEST, "Empty ID."); + } + + Document document = this.deleteDocument(id, city); + HttpResponse metricResponse = this.deleteMetric(city, document.getString("name"), document.getDate("dateObserved").getTime()); + + if (metricResponse.getStatusLine().getStatusCode() == HttpStatus.OK.value()) + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::OUT [" + HttpStatus.OK + "]"); + return new APIResponse(HttpStatus.OK, this.buildResponse(id)); + } + else + { + logger.debug("TrafficFlowObserved::deleteDataByID(" + city + ")::OUT [" + HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()) + "]"); + return new APIResponse(HttpStatus.valueOf(metricResponse.getStatusLine().getStatusCode()), metricResponse.getStatusLine().getReasonPhrase()); + } + } + catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + return new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR,"Server Error: " + ex.getMessage()); + } + } + + private Document deleteDocument(String id, City city) throws MongoDbConnectionException, DocumentNotFoundException, OpenTsdbOperationException + { + TrafficFlowObserved trafficFlowObserved = null; + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + + if (!mongoDB.connect()) + { + throw new MongoDbConnectionException(); + } + + MongoDatabase database = mongoDB.getDatabase(); + boolean found = false; + + List<String> collectionsName = getCollectionsByPrefix(database, city); + + Document document = null; + for (String collectionName: collectionsName) + { + MongoCollection<Document> collection = database.getCollection(collectionName); + document = collection.find(new BasicDBObject("_id", id)).first(); + + if (document != null) + { + found = true; + + DeleteResult result = collection.deleteOne(new Document("_id", id)); + + if (result.getDeletedCount() == 0) + { + throw new OpenTsdbOperationException("Could not be deleted the id requested."); + } + + break; + } + } + if (!found) + { + throw new DocumentNotFoundException("Document '" + id + "' not found."); + } + mongoDB.close(); + + return document; + } + + private HttpResponse deleteMetric( City city, String idSpiral, long timestamp) throws Exception + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + return opentsdbClient.Delete("trafficflowobserved.intensity", city.name(), idSpiral, timestamp); + } + + + private DatabaseAction saveDocument(TrafficFlowObserved record,Map<String, MongoCollection<Document>> collectionList, + List<String> trafficCols, MongoDatabase database, City city) + { + DatabaseAction databaseAction = null; + Document doc = null; + MongoCollection<Document> coll = null; + + try + { + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + LocalDateTime ldateObs =LocalDateTime.ofInstant(dateObs.toInstant(), ZoneId.of("UTC")); + int year = ldateObs.getYear(); + int month = ldateObs.getMonthValue(); + String yearMonth = year + "_" + (month<10?"0":"") + month; + + if (collectionList.containsKey(yearMonth)) + { + coll = collectionList.get(yearMonth); + } + else + { + String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_" + yearMonth).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(yearMonth, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (!trafficCols.contains(collectionName)) + { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + doc.append("dateObserved", dateObs); + + Date dateObsTo = null; + + if (record.getDateObservedTo() != null) + { + if (!record.getDateObservedTo().isEmpty() && record.getDateObservedTo().compareToIgnoreCase("null") != 0) + dateObsTo= Utils.ISO2Date(record.getDateObservedTo()); + } + if (dateObsTo != null) doc.append("dateObservedTo", dateObsTo); + + Date dateObsFrom = null; + if (record.getDateObservedFrom() != null) + { + if (!record.getDateObservedFrom().isEmpty() && record.getDateObservedFrom().compareToIgnoreCase("null") != 0) + dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + } + if (dateObsFrom != null) doc.append("dateObservedFrom", dateObsFrom); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.INSERTED, record.getId()); + } + catch (MongoWriteException e) + { + int errorCode = e.getCode(); + if (errorCode == 11000) + { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + + if (existing != null) + { + try + { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + //fields dateObservedFrom and dateObservedTo: if not set, use observation date + Date dateObsFrom = null; + if (record.getDateObservedFrom() != null) { + if (record.getDateObservedFrom().isEmpty() == false && record.getDateObservedFrom().compareToIgnoreCase("null") != 0) + dateObsFrom = Utils.ISO2Date(record.getDateObservedFrom()); + } + if (dateObsFrom != null) doc.append("dateObservedFrom", dateObsFrom); + Date dateObsTo = null; + if (record.getDateObservedTo() != null) { + if (record.getDateObservedTo().isEmpty() == false && record.getDateObservedTo().compareToIgnoreCase("null") != 0) + dateObsTo= Utils.ISO2Date(record.getDateObservedTo()); + } + if (dateObsTo != null) doc.append("dateObservedTo", dateObsTo); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.UPDATED); + + } + catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, ex.getMessage()); + } + } + } + } + catch (Exception ex) + { + logger.error(ex.getMessage(), ex); + databaseAction = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, ex.getMessage()); + } + return databaseAction; + + } + + private List<JSONObject> updateDocument(TrafficFlowObserved trafficFlowObserved, City city, String id) throws JSONException, MongoDbConnectionException, DocumentNotFoundException { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + + if (!mongoDB.connect()) + { + throw new MongoDbConnectionException(); + } + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(trafficFlowObserved.getDateObserved()); + + MongoCollection<Document> collection = this.getCollection(database, city, dateObs); + + Document documentFromDatabase = collection.find(Filters.eq("_id", id)).first(); + + if ( documentFromDatabase == null) + { + throw new DocumentNotFoundException("Document '" + id + "' not found."); + } + + Document document = Document.parse(Utils.Object2JSON(trafficFlowObserved)); + document.remove("id"); + //need to replace "context" for "@context" + document.put("@context", trafficFlowObserved.getContext()); + document.remove("context"); + //creation date is the same as original + document.append("dateCreated", documentFromDatabase.get("dateCreated")); + //modification date is now + document.append("dateModified", new Date()); + document.append("dateObserved", dateObs); + //fields dateObservedFrom and dateObservedTo: if not set, use observation date + Date dateObsFrom = null; + if (trafficFlowObserved.getDateObservedFrom() != null) + { + if (trafficFlowObserved.getDateObservedFrom().isEmpty() == false && trafficFlowObserved.getDateObservedFrom().compareToIgnoreCase("null") != 0) + dateObsFrom = Utils.ISO2Date(trafficFlowObserved.getDateObservedFrom()); + } + if (dateObsFrom != null) document.append("dateObservedFrom", dateObsFrom); + Date dateObsTo = null; + if (trafficFlowObserved.getDateObservedTo() != null) + { + if (trafficFlowObserved.getDateObservedTo().isEmpty() == false && trafficFlowObserved.getDateObservedTo().compareToIgnoreCase("null") != 0) + dateObsTo= Utils.ISO2Date(trafficFlowObserved.getDateObservedTo()); + } + if (dateObsTo != null) document.append("dateObservedTo", dateObsTo); + + collection.replaceOne(Filters.eq("_id", id), document); + + mongoDB.close(); + + return this.buildResponse(document,id); + } + + private MongoCollection<Document> getCollection(MongoDatabase database, City city, Date dateObserved) + { + int year = dateObserved.getYear() + 1900; + int month = dateObserved.getMonth() + 1; + String yearMonth = year + "_" + (month<10?"0":"") + month; + String collectionName = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_" + yearMonth).toLowerCase(); + return database.getCollection(collectionName); + } + + private List<String> getCollectionsByPrefix(MongoDatabase database, City city) + { + List<String> trafficCollections = new ArrayList<String>(); + //get names of collections, filter by model, and order in "descending" order (recent first) + String collectionNamePrefix = (TransportationDataModel.TRAFFICFLOWOBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> collectionsNames = database.listCollectionNames(); + for (String collectionName: collectionsNames) + { + if (collectionName.toLowerCase().startsWith(collectionNamePrefix)) + { + trafficCollections.add(collectionName.toLowerCase()); + } + } + Collections.sort(trafficCollections, Collections.reverseOrder()); //recent first + + return trafficCollections; + } + + private DatabaseAction saveMetric(TrafficFlowObserved trafficFlowObserved, City city) + { + DatabaseAction action = null; + + try + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + HttpResponse response = opentsdbClient.Post(new PointBuilder().addMetric("trafficflowobserved.intensity") + .addTimestamp( Objects.requireNonNull(Utils.ISO2Date(trafficFlowObserved.getDateObserved())).getTime() /1000) + .addValue(trafficFlowObserved.getIntensity()) + .addTag("city", city.name()) + .addTag("id_spiral",trafficFlowObserved.getName()).build()); + + if ( response == null) + { + action = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, "No response from the server"); + } + // + else if ( response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) + { + action = new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, response.getStatusLine().getReasonPhrase()); + } + else if ( response.getStatusLine().getStatusCode() == HttpStatus.OK.value() && + JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject().get("success").toString().equals("1")) + { + action = new DatabaseAction(DatabaseActionTypeEnum.INSERTED ); + } + } + catch (IOException ex) + { + logger.error(ex.getMessage(), ex); + return new DatabaseAction(DatabaseActionTypeEnum.NOT_INSERTED, "It could not connect with the opentsdb server."); + } + + return action; + } + + private HttpResponse updateMetric(TrafficFlowObserved trafficFlowObserved, City city) throws Exception + { + OpentsdbClient opentsdbClient = new OpentsdbClient(DBConfiguration.getOpentsdbUrl()); + + return opentsdbClient.Post(new PointBuilder().addMetric("trafficflowobserved.intensity") + .addTimestamp( Objects.requireNonNull(Utils.ISO2Date(trafficFlowObserved.getDateObserved())).getTime() /1000) + .addValue(trafficFlowObserved.getIntensity()) + .addTag("city", city.name()) + .addTag("id_spiral",trafficFlowObserved.getName()).build()); + } + + private void registry(TrafficFlowObserved trafficFlowObserved, City city, JSONArray jsonArray) + { + registry(trafficFlowObserved, city, jsonArray,null); + } + + private void registry(TrafficFlowObserved trafficFlowObserved, City city, JSONArray jsonArray, String message) + { + JSONObject jsonObject = new JSONObject(); + try + { + jsonObject.put("id", trafficFlowObserved.getId()); + if(message != null) + { + jsonObject.put("reason", message); + } + jsonArray.put(jsonObject); + } + catch (JSONException e) + { + logger.error("TrafficFlowObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + + private List<JSONObject> buildResponse( JSONArray arrInserted, JSONArray arrNotInserted, JSONArray arrUpdated, City city) + { + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + try + { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } + catch (JSONException e) + { + logger.error("TrafficFlowObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + return lstRes; + } + + private List<JSONObject> buildResponse( Document document, String id) throws JSONException { + List<JSONObject> jsonObjectList = new ArrayList<JSONObject>(); + JSONObject jsonObject = new JSONObject(); + + document.put("id", id); + jsonObject.put("updatedData", new JSONObject(document.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()))); + jsonObjectList.add(jsonObject); + return jsonObjectList; + } + + public List<JSONObject> buildResponse( String id) throws JSONException + { + List<JSONObject> jsonObjectList = new ArrayList<JSONObject>(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("deleted", id); + jsonObjectList.add(jsonObject); + return jsonObjectList; + } + + public class WorkerThread implements Runnable { + private List<Document> results = null; + FindIterable<Document> cursor = null; + String collectionName = ""; + + public WorkerThread(String collectionName, FindIterable<Document> cursor) { + results = new ArrayList<>(); + this.cursor = cursor; + this.collectionName = collectionName; + } + + @Override + public void run() { + if (cursor != null) { + long start = System.currentTimeMillis(); + cursor.into(results); + logger.info(collectionName + " --> " + (System.currentTimeMillis() - start)); + } + } + + public List<Document> getResults(){ + return results; + } + + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TransportStationController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TransportStationController.java new file mode 100644 index 0000000000000000000000000000000000000000..117d92f1e520077aa26400a6a2caa0e5c845b46f --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/TransportStationController.java @@ -0,0 +1,679 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTrip; +import com.tecnalia.urbanite.storage.DataModel.TouristTrip.TouristTripDataModel; +import com.tecnalia.urbanite.storage.DataModel.TransportStation.TransportStation; +import com.tecnalia.urbanite.storage.DataModel.TransportStation.TransportStationDataModel; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class TransportStationController extends GenericController implements IGenericController { + + private Logger logger = LoggerFactory.getLogger(TransportStationController.class); + private DBConfiguration.DBParams dbParams; + + + private MongoDBManager mongoDBGlobal = null; + + + + public TransportStationController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + this.mongoDBGlobal = new MongoDBManager(dbParams); + this.mongoDBGlobal.connect(); + } + + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("TransportStation::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<String, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + for (int i = 0; i < arrData.length(); i++) { + TransportStation record = TransportStation.createTransportStation(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("TransportStation::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + String collectionName = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + coll = database.getCollection(collectionName); + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported= Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TransportStation::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("TransportStation::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + + + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("TransportStation::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + + + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TransportStation' objects)"); + } + + } catch (Exception e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'TransportStation' objects)"+ e2.getMessage()); + } + + logger.debug("TransportStation::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + } + + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("TransportStation::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + try { + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(TransportStation.class, filters)) { + //check the return fields + if (Utils.typeHasFields(TransportStation.class, returnFields)) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String collectionName = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + coll = database.getCollection(collectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateModified", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("TransportStation::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + + querySort.append("dateLastReported", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + + } catch (JSONException e) { + logger.error("TransportStation::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + iterator.close(); + + //reached limit? + + + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.transportStation + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.transportStation + "' data model's fields."); + } + + logger.debug("TransportStation::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("TransportStation::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + try { + TransportStation record = TransportStation.createTransportStation(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + Date dateLastReported = null; + if (record.getDateLastReported() != null) { + if (record.getDateLastReported().isEmpty() == false && record.getDateLastReported().compareToIgnoreCase("null") != 0) + dateLastReported = Utils.ISO2Date(record.getDateLastReported()); + } + if (dateLastReported != null) doc.append("dateLastReported", dateLastReported); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + //o.put("updatedData", doc); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse--> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('TransportStation' object)"); + } + + } catch (Exception mEx) { + System.out.println("Exception: " + mEx.getMessage()); + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Server Error: " + mEx.getMessage()); + } + + logger.debug("TransportStation::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "TransportStation"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "startDate", collNamePrefix, "TransportStation", TransportStation.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{ \r\n" + + " \"id\": \"urn:ngsi-ld:Station:Station:MNCA-STram-L02-AP-T2\", \r\n" + + " \"type\": \"Station\", \r\n" + + " \"name\": \"NCE-Tram-Station-L02-AP-T2\", \r\n" + + " \"alternateName\": \"Nice - Tramway Station Description - L02-AP-T2\", \r\n" + + " \"description\": \"Description and services provided in the station\", \r\n" + + " \"seeAlso\": \"http://tramway.nice.fr/wp-content/uploads/2019/10/BD_pocket_plan_MAJ03_2019_20082019.pdf\", \r\n" + + " \"location\": { \r\n" + + " \"type\": \"Point\", \r\n" + + " \"coordinates\": [ \r\n" + + " 43.66481, \r\n" + + " 7.196545 \r\n" + + " ] \r\n" + + " }, \r\n" + + " \"address\": { \r\n" + + " \"addressCountry\": \"FR\", \r\n" + + " \"addressLocality\": \"Nice\", \r\n" + + " \"streetAddress\": \"Airport - Terminal 2 - Door A2\" \r\n" + + " }, \r\n" + + " \"areaServed\": \"Nice Airport\", \r\n" + + " \"dateLastReported\": \"2020-03-17T08:45:00Z\", \r\n" + + " \"dateObserved\": \"2020-03-17T08:45:00Z\", \r\n" + + " \"stationType\": [ \r\n" + + " \"tram\" \r\n" + + " ], \r\n" + + " \"locationType\": 1, \r\n" + + " \"levelId\": 0, \r\n" + + " \"zoneId\": \"B\", \r\n" + + " \"wheelChairAccessible\": 1, \r\n" + + " \"openingHoursSpecification\": [ \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Monday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Tuesday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Wednesday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Thursday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Friday\", \r\n" + + " \"opens\": \"07:00:00\", \r\n" + + " \"closes\": \"22:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Saturday\", \r\n" + + " \"opens\": \"08:00:00\", \r\n" + + " \"closes\": \"23:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"Sunday\", \r\n" + + " \"opens\": \"08:30:00\", \r\n" + + " \"closes\": \"21:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " }, \r\n" + + " { \r\n" + + " \"dayOfWeek\": \"PublicHolidays\", \r\n" + + " \"opens\": \"08:30:00\", \r\n" + + " \"closes\": \"21:00:00\", \r\n" + + " \"validFrom\": \"2021-01-01T00:00:00\", \r\n" + + " \"validThrough\": \"2021-12-31T23:59:59\" \r\n" + + " } \r\n" + + " ], \r\n" + + " \"owner\": [ \r\n" + + " \"uri:ngsi:StreetRetail\" \r\n" + + " ], \r\n" + + " \"contractingAuthority\": \"MNCA - Metropole Nice Cote d'Azur\", \r\n" + + " \"contractingCompagny\": \"R\\u00e9gie Ligne d'Azur\", \r\n" + + " \"contactPoint\": { \r\n" + + " \"url\": \"uri:ngsi:www.lignesdazur.com\" \r\n" + + " }, \r\n" + + " \"webSite\": \"https://tramway.nice.fr/Plan-Station-L02-AP-T2.pdf\", \r\n" + + " \"instalationMode\": \"ground\", \r\n" + + " \"dimension\": { \r\n" + + " \"length\": 300, \r\n" + + " \"width\": 25, \r\n" + + " \"thickness\": 6.35 \r\n" + + " }, \r\n" + + " \"inventory\": { \r\n" + + " \"nbOfIOPoint\": 2, \r\n" + + " \"nbOfLane\": 1, \r\n" + + " \"nbOfPlatform\": 1, \r\n" + + " \"PlatformType\": [ \r\n" + + " \"lateral\" \r\n" + + " ] \r\n" + + " }, \r\n" + + " \"stationConnected\": [ \r\n" + + " { \r\n" + + " \"stationType\": \"tram\", \r\n" + + " \"linesConnected\": [ \r\n" + + " \"Tram 2 - CADAM / Nikaia\", \r\n" + + " \"Tram 3 - Saint Isidore / Stade Allianz Riviera\" \r\n" + + " ] \r\n" + + " }, \r\n" + + " { \r\n" + + " \"stationType\": \"train\", \r\n" + + " \"linesConnected\": [ \r\n" + + " \"Gare SNCF Nice Saint Augustin (600m)\" \r\n" + + " ] \r\n" + + " }, \r\n" + + " { \r\n" + + " \"stationType\": \"bus\", \r\n" + + " \"linesConnected\": [ \r\n" + + " \"L20 - Giono / Les Pugets\", \r\n" + + " \"L20 - Centre Commercial St Isidore\", \r\n" + + " \"L21 - Le Gu\\u00e9 / Polygone Riviera\", \r\n" + + " \"L54 - Centre Commercial Cap 3000 - St Jeannet\", \r\n" + + " \"L90 - La Bolline\", \r\n" + + " \"91 Auron\", \r\n" + + " \"L92 - Isola 2000\" \r\n" + + " ] \r\n" + + " } \r\n" + + " ], \r\n" + + " \"services\": { \r\n" + + " \"purchaseDevice\": true, \r\n" + + " \"interactiveDevice\": true, \r\n" + + " \"timetableDevice\": true, \r\n" + + " \"voiceDevice\": true, \r\n" + + " \"informationBoardDevice\": true, \r\n" + + " \"messageDevice\": false, \r\n" + + " \"shelters\": true, \r\n" + + " \"restBench\": false, \r\n" + + " \"emergencyPhone\": false, \r\n" + + " \"videoSurveillance\": true, \r\n" + + " \"defibrillator\": false, \r\n" + + " \"wheelChairAccessible\": true \r\n" + + " }, \r\n" + + " \"paymentAccepted\": [ \r\n" + + " \"Cash\", \r\n" + + " \"CreditCard\" \r\n" + + " ], \r\n" + + " \"currencyAccepted\": [ \r\n" + + " \"EUR\" \r\n" + + " ], \r\n" + + " \"constructionDate\": \"2016-19-08\", \r\n" + + " \"commissioningDate\": \"2018-09-15\", \r\n" + + " \"architect\": \"Nice Architecture\", \r\n" + + " \"featuredArtist \": [ \r\n" + + " \"Leopold\", \r\n" + + " \"De Renaiss\" \r\n" + + " ], \r\n" + + " \"@context\": [ \r\n" + + " \"https://smartdatamodels.org/context.jsonld\", \r\n" + + " \"https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\" \r\n" + + " ] \r\n" + + "} "; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("TransportStation::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "TransportStation",TransportStation.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "TransportStation",TransportStation.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + String collNamePrefix = (TransportStationDataModel.TransportStation + "_" + city ).toLowerCase(); + + return this.deleteDataByID(city, id, collNamePrefix, "TransportStation"); + + } + +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/VehicleController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/VehicleController.java new file mode 100644 index 0000000000000000000000000000000000000000..37e1fa01a211fd95dcfb8c9de6fbdd3af6c443db --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/VehicleController.java @@ -0,0 +1,458 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoWriteException; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.Transportation.TransportationDataModel; +import com.tecnalia.urbanite.storage.DataModel.Transportation.Vehicle; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class VehicleController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(VehicleController.class); + + public VehicleController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("Vehicle::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + String CollectionName = (TransportationDataModel.VEHICLEOBSERVED+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + for (int i = 0; i < arrData.length(); i++) { + Vehicle record = Vehicle.createVehicle(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("Vehicle::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + Date observationDateTime = null; + + if (record.getObservationDateTime() != null) + { + if (!record.getObservationDateTime().isEmpty() && record.getObservationDateTime().compareToIgnoreCase("null") != 0) + observationDateTime= Utils.ISO2Date(record.getObservationDateTime()); + } + if (observationDateTime != null) doc.append("observationDateTime", observationDateTime); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + Date observationDateTime = null; + + if (record.getObservationDateTime() != null) + { + if (!record.getObservationDateTime().isEmpty() && record.getObservationDateTime().compareToIgnoreCase("null") != 0) + observationDateTime= Utils.ISO2Date(record.getObservationDateTime()); + } + if (observationDateTime != null) doc.append("observationDateTime", observationDateTime); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Vehicle::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("Vehicle::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("Vehicle::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'GtfsShape' objects)"); + } + + logger.debug("GtfsShape::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("Vehicle::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(Vehicle.class, filters)) { + //check the return fields + if (Utils.typeHasFields(Vehicle.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + MongoCollection<Document> coll = null; + String CollectionName = (TransportationDataModel.VEHICLEOBSERVED+ "_" + city).toLowerCase(); + coll = database.getCollection(CollectionName); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("observationDateTime", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("Vehicle::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateModified", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateModified") == false) returnFields.add("dateModified"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("Vehicle::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + TransportationDataModel.VEHICLEOBSERVED + "' data model's fields."); + } + } + else { + //filtersOk false + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + TransportationDataModel.VEHICLEOBSERVED + "' data model's fields."); + } + logger.debug("Vehicle::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + logger.debug("Vehicle::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + Vehicle record = Vehicle.createVehicle(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + String collectionName = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('PointOfInterest' object)"); + } + + logger.debug("Vehicle::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "Vehicle"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "observationDateTime", collNamePrefix, "Vehicle", Vehicle.class); + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("Vehicle::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "Vehicle",Vehicle.class); + + + } + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "Vehicle",Vehicle.class); + + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (TransportationDataModel.VEHICLEOBSERVED + "_" + city).toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "Vehicle"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/WeatherObservedController.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/WeatherObservedController.java new file mode 100644 index 0000000000000000000000000000000000000000..eadfa7278de334eaa1394c6398136c412e8d13d7 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/WeatherObservedController.java @@ -0,0 +1,557 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.json.JsonWriterSettings; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import com.mongodb.BasicDBObject; +import com.mongodb.MongoException; +import com.mongodb.MongoWriteException; +import com.mongodb.client.DistinctIterable; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.MongoIterable; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.Projections; +import com.mongodb.client.result.DeleteResult; +import com.tecnalia.urbanite.storage.APIResponse; +import com.tecnalia.urbanite.storage.DB.DBConfiguration; +import com.tecnalia.urbanite.storage.DB.MongoDBManager; +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.DataModel.TransportStation.TransportStation; +import com.tecnalia.urbanite.storage.DataModel.Weather.WeatherDataModel; +import com.tecnalia.urbanite.storage.DataModel.Weather.WeatherObserved; +import com.tecnalia.urbanite.storage.Utils.JsonDateTimeConverter; +import com.tecnalia.urbanite.storage.Utils.Utils; + +public class WeatherObservedController extends GenericController implements IGenericController { + + private DBConfiguration.DBParams dbParams; + private Logger logger = LoggerFactory.getLogger(WeatherObservedController.class); + + public WeatherObservedController() { + super(); + this.dbParams = DBConfiguration.getDBConfiguration(DBConfiguration.DBTYPE.MONGODB); + } + + @Override + public APIResponse insertData(City city, String data) { + + logger.debug("WeatherObserved::insertData(" + city + ")::IN"); + APIResponse res = new APIResponse(); + + JSONArray arrInserted = new JSONArray(); + JSONArray arrNotInserted = new JSONArray(); + JSONArray arrUpdated = new JSONArray(); + + Map<Integer, MongoCollection<Document>> collectionList = new HashMap<>(); + + try { + JSONArray arrData = new JSONArray(data); + if (arrData.length() > 0) { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + MongoCollection<Document> coll = null; + + //we'll get the collection names, to check later if the collection where we'll insert an element exists or not, to create indexes. + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED+ "_" + city + "_").toLowerCase(); + + List<String> weatherColls = new ArrayList<String>(); + + weatherColls = mongoDB.getModelCollectionNames(collNamePrefix,SortingMode.DESC ); + for (int i = 0; i < arrData.length(); i++) { + WeatherObserved record = WeatherObserved.createWeatherObserved(arrData.getString(i)); + if (record.isValid() == false) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", "Wrong input data, missing some required field(s) or wrong values."); + arrNotInserted.put(o); + } catch (JSONException e) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating JSON for invalid record: " + e.getMessage()); + } + } + else { + + Document doc = null; + try { + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + LocalDateTime ldateObs =LocalDateTime.ofInstant(dateObs.toInstant(), ZoneId.of("UTC")); + int year = ldateObs.getYear(); + + if (collectionList.containsKey(year)) + coll = collectionList.get(year); + else { + String collectionName = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_" + year).toLowerCase(); + coll = database.getCollection(collectionName); + collectionList.put(year, coll); + + //create an index in "dateObserved" if the collection is new (not in previous collections read) + if (weatherColls.contains(collectionName) == false) { + coll.createIndex(Indexes.descending("dateObserved")); + } + } + + + doc = Document.parse(Utils.Object2JSON(record)); + //need to replace "id" for "_id" + doc.put("_id", record.getId()); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + doc.append("dateObserved", dateObs); + + Date d = new Date(); + doc.append("dateCreated", d); + doc.append("dateModified", d); + + coll.insertOne(doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrInserted.put(o); + + } catch (MongoWriteException e) { + int errorCode = e.getCode(); + if (errorCode == 11000) { + //duplicated key --> update + Document existing = coll.find(Filters.eq("_id", record.getId())).first(); + if (existing != null) { + try { + doc.remove("id"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + //need to transform observation date + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", record.getId()),doc); + JSONObject o = new JSONObject(); + o.put("id", record.getId()); + arrUpdated.put(o); + } catch (Exception ex) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", ex.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + } catch (Exception e) { + JSONObject o = new JSONObject(); + try { + o.put("id", record.getId()); + o.put("reason", e.getMessage()); + arrNotInserted.put(o); + } catch (JSONException e1) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating JSON: " + e.getMessage()); + } + } + } + } + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + try { + JSONObject o = new JSONObject(); + o.put("inserted", arrInserted); + o.put("notInserted", arrNotInserted); + o.put("updated", arrUpdated); + lstRes.add(o); + } catch (JSONException e) { + logger.error("WeatherObserved::insertData(" + city +"). Error creating final response JSON: " + e.getMessage()); + } + res.setStatus(HttpStatus.OK); + res.setData(lstRes); + + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + + } else { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Weather Observation' objects)"); + } + + } catch (JSONException e2) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format (list of 'Weather Observation' objects)"); + } + + logger.debug("WeatherObserved::insertData(" + city + ")::OUT [" + res.getStatus() + "]: " + arrInserted.length() + " element(s) inserted, " + arrNotInserted.length() + " element(s) not inserted, " + arrUpdated.length() + " element(s) updated."); + return res; + + } + + @Override + public APIResponse getTDataRange(City city, Date startDate, Date endDate, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + logger.debug("WeatherObserved::getTDataRange(" + city + ")::IN - Request for data with time range [start=" + startDate + "; end=" + endDate + "] and limit=" + limit); + APIResponse res = new APIResponse(); + + List<JSONObject> retData = new ArrayList<JSONObject>(); + + //check filters + if (Utils.typeHasFieldsJSON(WeatherObserved.class, filters)) { + //check the return fields + if (Utils.typeHasFields(WeatherObserved.class, returnFields)) { + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + int startYear = 0; + int endYear = 0; + if (startDate != null) startYear = startDate.getYear() + 1900; + if (endDate != null) endYear = endDate.getYear() + 1900; + + //get the different collections between dates + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + MongoIterable<String> colNames = database.listCollectionNames(); + List<String> weatherColls = new ArrayList<String>(); + for (String collectionName: colNames) { + if (collectionName.toLowerCase().startsWith(collNamePrefix)) { + + try { + int year = Integer.parseInt(collectionName.substring(collNamePrefix.length())); + //no errors --> check if it's in range + boolean addToList = true; + if (startYear > 0 && year < startYear) addToList = false; + if (endYear > 0 && year > endYear) addToList = false; + if (addToList) + weatherColls.add(collectionName.toLowerCase()); + } + catch (Exception e) { + //not "YEAR" format --> omit table + } + + } + } + + if (sort.compareTo(SortingMode.ASC) == 0) + Collections.sort(weatherColls); + else + Collections.sort(weatherColls, Collections.reverseOrder()); + + //Filters + BasicDBObject queryFilters = new BasicDBObject(); + //dates + BasicDBObject dateRange = new BasicDBObject (); + if (startDate != null) dateRange.put("$gte", startDate); + if (endDate != null) dateRange.put("$lte", endDate); + if (dateRange.isEmpty() == false) queryFilters.append("dateObserved", dateRange); + //fields + try { + Iterator<String> keys = filters.keys(); + while(keys.hasNext()) { + String fieldName = keys.next(); + Object fieldValue = filters.get(fieldName); + queryFilters.append(fieldName, fieldValue); + } + } catch (JSONException e) { + logger.error("WeatherObserved::getTDataRange(" + city + "):: Error creating filters [" + filters + "]: " + e.getMessage()); + } + + //Sorting + BasicDBObject querySort = new BasicDBObject(); + //querySort.append("dateObserved", -1); //recent first + querySort.append("dateObserved", sort.getOrder()); + + //return fields + Bson projection = null; + if (returnFields.isEmpty() == false) { + //Always id (included by default) and dateObserved + if (returnFields.contains("dateObserved") == false) returnFields.add("dateObserved"); + projection = Projections.fields(Projections.include(returnFields)); + } + + int currentLimit = limit; + //search the element in collections + for (String collectionName: weatherColls) { + + MongoCollection<Document> coll = database.getCollection(collectionName); + + FindIterable<Document> cursor; + if (currentLimit > 0) + cursor = coll.find(queryFilters).projection(projection).sort(querySort).limit(currentLimit); + else + cursor = coll.find(queryFilters).projection(projection).sort(querySort); + + MongoCursor<Document> iterator = cursor.iterator(); + while(iterator.hasNext()) { + Document doc = iterator.next(); + //if return fields are set, remove id if not asked + if (returnFields.isEmpty() == false && returnFields.contains("id") == false) doc.remove("_id"); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + //need to replace "_id" field to "id" + sDoc = sDoc.replace("\"_id\":", "\"id\":"); + try { + JSONObject jElem= new JSONObject(sDoc); + retData.add(jElem); + } catch (JSONException e) { + logger.error("WeatherObserved::getTDataRange(" + city + "):: Error creating JSON from String (" + sDoc + "): " + e.getMessage()); + } + } + + //reached limit? + int total = retData.size(); + if (total == limit) + break; + else + currentLimit = limit - total; + } + + res.setStatus(HttpStatus.OK); + res.setData(retData); + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + } + else { + //wrong return fields + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong fields to be returned, please check '" + DataModel.weatherObserved + "' data model's fields."); + } + } + else { + //wrong filters + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong filters, please check '" + DataModel.weatherObserved + "' data model's fields."); + } + + logger.debug("WeatherObserved::getTDataRange(" + city + ")::OUT [" + res.getStatus() + "]: Returning " + retData.size() + " element(s)"); + return res; + } + + @Override + public APIResponse updateData(City city, String id, String data) { + + logger.debug("WeatherObserved::updateData(" + city + ")::IN: Request to update document with id = " + id); + APIResponse res = new APIResponse(); + + WeatherObserved record = WeatherObserved.createWeatherObserved(data); + if (record != null) { + + if (record.isValid() == false) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, some required field(s) missing."); + } + else { + if (record.getId().compareTo(id) != 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Wrong input data, IDs are different."); + } + else { + + MongoDBManager mongoDB = new MongoDBManager(dbParams); + if (mongoDB.connect()) { + + MongoDatabase database = mongoDB.getDatabase(); + + Date dateObs = Utils.ISO2Date(record.getDateObserved()); + int year = dateObs.getYear() + 1900; + String collectionName = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_" + year).toLowerCase(); + MongoCollection<Document> coll = database.getCollection(collectionName); + + Document existing = coll.find(Filters.eq("_id", id)).first(); + if (existing != null) { + try { + //Document doc = Document.parse(data); + Document doc = Document.parse(Utils.Object2JSON(record)); + doc.remove("id"); + //need to replace "context" for "@context" + doc.put("@context", record.getContext()); + doc.remove("context"); + + //creation date is the same as original + doc.append("dateCreated", existing.get("dateCreated")); + //modification date is now + doc.append("dateModified", new Date()); + doc.append("dateObserved", dateObs); + + coll.replaceOne(Filters.eq("_id", id),doc); + + res.setStatus(HttpStatus.OK); + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + JSONObject o = new JSONObject(); + + doc.put("id", id); + String sDoc = doc.toJson(JsonWriterSettings.builder().dateTimeConverter(new JsonDateTimeConverter()).build()); + o.put("updatedData", new JSONObject(sDoc)); + + lstRes.add(o); + res.setData(lstRes); + } catch (Exception e) { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError(e.getMessage()); + } + } + else { + res.setStatus(HttpStatus.NOT_FOUND); + res.setError("Document '" + id + "' not found."); + } + + mongoDB.close(); + } + else { + res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR); + res.setError("Cann't connect to database."); + } + + } + } + } + else { + //can't parse --> error + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Input data is not in required format ('Weather Observation' object)"); + } + + logger.debug("WeatherObserved::updateData(" + city + ")::OUT [" + res.getStatus() + "]"); + return res; + } + + @Override + public APIResponse getDataByID(City city, String id) { + + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + return this.getDataByID(city, id, collNamePrefix, "WeatherObserved"); + } + + @Override + public APIResponse getTData(City city, JSONObject filters, List<String> returnFields, int limit, SortingMode sort) { + + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + return this.getTData(city, filters, returnFields, limit, sort, "dateObserved", collNamePrefix, "WeatherObserved", WeatherObserved.class); + + } + + @Override + public JSONObject getExample() { + + JSONObject res = new JSONObject(); + + String example = "{\r\n" + + " \"id\": \"urn:ngsi-ld:WeatherObserved:Spain-WeatherObserved-Valladolid-2016-11-30T07:00:00.00Z\",\r\n" + + " \"type\": \"WeatherObserved\",\r\n" + + " \"dateObserved\": \"2016-11-30T07:00:00.00Z\",\r\n" + + " \"temperature\": 3.3,\r\n" + + " \"precipitation\": 0,\r\n" + + " \"atmosphericPressure\": 1024,\r\n" + + " \"pressureTendency\": 0.5,\r\n" + + " \"refDevice\": \"urn:ngsi-ld:Device:device-0A3478\",\r\n" + + " \"source\": \"http://www.aemet.es\",\r\n" + + " \"location\": {\r\n" + + " \"type\": \"Point\",\r\n" + + " \"coordinates\": [\r\n" + + " -4.754444444,\r\n" + + " 41.640833333\r\n" + + " ]\r\n" + + " },\r\n" + + " \"address\": {\r\n" + + " \"addressLocality\": \"Valladolid\",\r\n" + + " \"addressCountry\": \"ES\"\r\n" + + " },\r\n" + + " \"dataProvider\": \"TEF\",\r\n" + + " \"relativeHumidity\": 1,\r\n" + + " \"streamGauge\": 50,\r\n" + + " \"snowHeight\": 20,\r\n" + + " \"windDirection\": 120,\r\n" + + " \"windSpeed\": 10,\r\n" + + " \"uvIndexMax\": 1,\r\n" + + " \"@context\": [\r\n" + + " \"https://smartdatamodels.org/context.jsonld\"\r\n" + + " ]\r\n" + + "}"; + + try { + res = new JSONObject(example); + } catch (JSONException e) { + logger.error("WeatherObserved::getExample:: Error creating example JSON from String(" + example + "): " + e.getMessage()); + } + return res; + } + + + @Override + public APIResponse getDistinct(City city, String[] fields) { + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, fields, collNamePrefix, "WeatherObserved",WeatherObserved.class); + + } + + @Override + public APIResponse getDistinct(City city, String field) { + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + + return this.getDistinct(city, field, collNamePrefix, "WeatherObserved",WeatherObserved.class); + + } + + @Override + public APIResponse deleteDataByID(City city, String id) { + + String collNamePrefix = (WeatherDataModel.WEATHEROBSERVED + "_" + city + "_").toLowerCase(); + return this.deleteDataByID(city, id, collNamePrefix, "WeatherObserved"); + + } + +} diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/DatabaseAction.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/DatabaseAction.java new file mode 100644 index 0000000000000000000000000000000000000000..9c103c767b9d66bb2833e41f592c0e56756ea794 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/DatabaseAction.java @@ -0,0 +1,36 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers.models; + +import com.tecnalia.urbanite.storage.controllers.models.enums.DatabaseActionTypeEnum; + +public class DatabaseAction +{ + public DatabaseActionTypeEnum actionType; + public String id; + public String reason; + + public DatabaseAction(DatabaseActionTypeEnum actionType) + { + this(actionType, null); + } + + public DatabaseAction(DatabaseActionTypeEnum actionType, String reason) + { + this.actionType = actionType; + this.reason = reason; + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/enums/DatabaseActionTypeEnum.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/enums/DatabaseActionTypeEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..a9edf2b06c93d18dfe3ee8a382dc98491c95de27 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/controllers/models/enums/DatabaseActionTypeEnum.java @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.controllers.models.enums; + +public enum DatabaseActionTypeEnum +{ + INSERTED, + UPDATED, + NOT_INSERTED +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/DocumentNotFoundException.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/DocumentNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..a6ccb584db31a0949d83e169e5e68e211e14e630 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/DocumentNotFoundException.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.exception; + +public class DocumentNotFoundException extends Exception +{ + public DocumentNotFoundException(String errorMessage) + { + super(errorMessage); + } + public DocumentNotFoundException() + { + super(); + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/MongoDbConnectionException.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/MongoDbConnectionException.java new file mode 100644 index 0000000000000000000000000000000000000000..e4e088cb5c80f9bad4157ab4a8390ad7c15f11d5 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/MongoDbConnectionException.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.exception; + +public class MongoDbConnectionException extends Exception +{ + public MongoDbConnectionException(String errorMessage) + { + super(errorMessage); + } + public MongoDbConnectionException() + { + super(); + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/OpenTsdbOperationException.java b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/OpenTsdbOperationException.java new file mode 100644 index 0000000000000000000000000000000000000000..17ca09a0598109c73cc1c36f703909e70859d5f9 --- /dev/null +++ b/openDataRetrieval/shared/src/main/java/com/tecnalia/urbanite/storage/exception/OpenTsdbOperationException.java @@ -0,0 +1,28 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.storage.exception; + +public class OpenTsdbOperationException extends Exception +{ + public OpenTsdbOperationException(String errorMessage) + { + super(errorMessage); + } + public OpenTsdbOperationException() + { + super(); + } +} \ No newline at end of file diff --git a/openDataRetrieval/shared/src/main/resources/application.properties b/openDataRetrieval/shared/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..3cb66a8cb1326910d12917b5f5dc592c29c4c815 --- /dev/null +++ b/openDataRetrieval/shared/src/main/resources/application.properties @@ -0,0 +1,5 @@ +application-description=@project.description@ +application-version=@project.version@ +logging.level.org.springframework.boot.autoconfigure=ERROR +logging.level.com.tecnalia.urbanite.storage=DEBUG + diff --git a/openDataRetrieval/shared/src/main/resources/static/datamodels.html b/openDataRetrieval/shared/src/main/resources/static/datamodels.html new file mode 100644 index 0000000000000000000000000000000000000000..27f521d06575e03301dbc4c8386585e9fe46c53c --- /dev/null +++ b/openDataRetrieval/shared/src/main/resources/static/datamodels.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="ISO-8859-1"> +<title>URBANITE - Data Models</title> +</head> +<body> +<p> Data Model </p> +</body> +</html> \ No newline at end of file diff --git a/openDataRetrieval/shared/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java b/openDataRetrieval/shared/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java new file mode 100644 index 0000000000000000000000000000000000000000..eff4c40d3683ca586ccd3e136a19e2ee320265b6 --- /dev/null +++ b/openDataRetrieval/shared/src/test/java/com/tecnalia/urbanite/storage/functional/StorageFunctionalTest.java @@ -0,0 +1,456 @@ +// +//package com.tecnalia.urbanite.storage.functional; +// +//import com.google.gson.*; +//import com.tecnalia.urbanite.storage.DataModel.AggregatorEnum; +//import com.tecnalia.urbanite.storage.Utils.Utils; +//import org.apache.http.HttpResponse; +//import org.apache.http.client.methods.HttpDelete; +//import org.apache.http.client.methods.HttpGet; +//import org.apache.http.client.methods.HttpPost; +//import org.apache.http.client.methods.HttpPut; +//import org.apache.http.conn.ssl.NoopHostnameVerifier; +//import org.apache.http.conn.ssl.TrustAllStrategy; +//import org.apache.http.entity.StringEntity; +//import org.apache.http.impl.client.HttpClientBuilder; +//import org.apache.http.ssl.SSLContextBuilder; +//import org.apache.http.util.EntityUtils; +//import org.junit.jupiter.api.Assertions; +//import org.junit.jupiter.api.Test; +//import org.springframework.http.HttpStatus; +// +//import java.io.BufferedReader; +//import java.io.IOException; +//import java.io.InputStream; +//import java.io.InputStreamReader; +//import java.net.URLEncoder; +//import java.nio.charset.StandardCharsets; +//import java.security.KeyManagementException; +//import java.security.KeyStoreException; +//import java.security.NoSuchAlgorithmException; +//import java.text.DateFormat; +//import java.text.SimpleDateFormat; +//import java.time.Duration; +//import java.util.Date; +//import java.util.TimeZone; +//import java.util.concurrent.ThreadLocalRandom; +// +//public class StorageFunctionalTest +//{ +// private final static String INSERT_URI = getApiUrl() + "/data/insertTData/trafficFlowObserved/bilbao"; +// private final static String UPDATE_URI = getApiUrl() + "/data/updateTData/trafficFlowObserved/bilbao"; +// private final static String DELETE_URI = getApiUrl() + "/data/deleteTData/trafficFlowObserved/bilbao"; +// private final static String AGGREGATE_URI = getApiUrl()+ "/data/aggregate/trafficFlowObserved/bilbao"; +// +// private static final Gson gson = new GsonBuilder() +// .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) +// .create(); +// +// public static String getApiUrl() +// { +// return System.getenv("API_TEST_URL") != null ? System.getenv("API_TEST_URL") : "http://localhost"; +// } +// +// @Test +// public void insert_empty() +// { +// try +// { +// HttpPost post = new HttpPost( INSERT_URI); +// post.addHeader("content-type", "application/json"); +// post.setEntity(new StringEntity("{}")); +// HttpResponse response = HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( post ); +// +// Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode()); +// +// String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); +// Assertions.assertEquals("Input data is not in required format (list of 'Traffic Flow Observation' objects)", +// JsonParser.parseString(responseString).getAsJsonObject().get("Error").getAsString()); +// +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// @Test +// public void insert_as_not_array() +// { +// try +// { +// String template = readFromInputStream("/traffic-flow-observed-template.json"); +// HttpResponse response = this.buildPost(template); +// Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode()); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// +// //Inserto un elemento +// @Test +// public void insert_one_element_as_array() +// { +// try +// { +// // inserto +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //Inserto dos elementos +// @Test +// public void insert_two_elements_as_array() +// { +// try +// { +// String trafficElement1 = this.getTrafficElement(); +// String trafficElement2 = this.getTrafficElement(); +// +// String trafficElementAsArray = "["+ trafficElement1 + "," + trafficElement2+ "]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement1).getAsJsonObject().get("id").toString() + "}," +// + "{\"id\":" + JsonParser.parseString(trafficElement2).getAsJsonObject().get("id").toString()+ "}]", jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement1); +// this.assertIntensity(trafficElement2); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //Inserto un elemento y lo vuelvo a insertar +// @Test +// public void insert_one_element_as_array_again() +// { +// try +// { +// // inserto +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // inserto de nuevo +// trafficElement = this.modifyIntensity(trafficElement); +// trafficElementAsArray = "["+trafficElement+"]"; +// response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //update un elemento +// @Test +// public void update_one_element() +// { +// try +// { +// // primero lo inserto para poder hacer luego el update +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // hago el update +// trafficElement = this.modifyIntensity(trafficElement); +// response = this.buildPut(trafficElement); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals(JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").getAsInt(), +// jsonObject.get("updatedData").getAsJsonObject().get("intensity").getAsInt()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// //delete un elemento +// @Test +// public void delete_one_element() +// { +// try +// { +// // primero lo inserto para poder hacer luego el delete +// String trafficElement = this.getTrafficElement(); +// String trafficElementAsArray = "["+trafficElement+"]"; +// HttpResponse response = this.buildPost(trafficElementAsArray); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Assertions.assertEquals("[{\"id\":" + JsonParser.parseString(trafficElement).getAsJsonObject().get("id").toString() + "}]",jsonObject.get("inserted").toString()); +// Assertions.assertEquals("[]",jsonObject.get("updated").toString()); +// Assertions.assertEquals("[]",jsonObject.get("notInserted").toString()); +// +// Thread.sleep(3000); +// +// this.assertIntensity(trafficElement); +// +// // hago el delete +// response = this.buildDelete(trafficElement); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// Assertions.assertEquals(JsonParser.parseString(trafficElement).getAsJsonObject().get("id").getAsString(), +// jsonObject.get("deleted").getAsString()); +// +// Thread.sleep(3000); +// +// // Lo vuelvo a consultar y no estĂ¡ +// Date start = Date.from(new Date().toInstant().minus(Duration.ofMinutes(5))); +// String id_spiral = JsonParser.parseString(trafficElement).getAsJsonObject().get("name").toString(); +// response = this.buildQuery(start, new Date(), AggregatorEnum.SUM, "{\"id_spiral\":\"" + id_spiral +"\"}"); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// Assertions.assertEquals("[]",EntityUtils.toString(response.getEntity())); // empty +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// +// //Comprueba que funciona la agregaciĂ³n. +// @Test +// public void aggregate_elements_sum() +// { +// try +// { +// int intensitySum = 0; +// String startDate = ""; +// String endDate = ""; +// +// StringBuilder trafficElementAsArray = new StringBuilder(); +// trafficElementAsArray.append("["); +// int elements = ThreadLocalRandom.current().nextInt(0, 20); +// for ( int i = 1; i<= elements ; i++) +// { +// String trafficElement = this.getTrafficElement(String.valueOf(i)); // Pass the intensity to evict duplicated spirals. +// intensitySum += JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").getAsInt(); +// if ( i==1) +// { +// startDate = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").getAsString(); +// } +// else if ( i == elements) +// { +// endDate = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").getAsString(); +// } +// trafficElementAsArray.append(trafficElement); +// if ( i != elements ) // We add the colon only when no is the last iteration. +// { +// trafficElementAsArray.append(","); +// } +// Thread.sleep(100); +// } +// trafficElementAsArray.append("]"); +// +// HttpResponse response = this.buildPost(trafficElementAsArray.toString()); +// Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()); +// JsonObject jsonObject = JsonParser.parseString(EntityUtils.toString(response.getEntity())).getAsJsonObject(); +// +// Thread.sleep(3000); +// +// this.assertSumIntensity(Utils.ISO2Date(startDate), Utils.ISO2Date(endDate), intensitySum); +// } +// catch (Exception ex) +// { +// Assertions.fail(); +// } +// } +// +// private String getTrafficElement() throws IOException +// { +// return this.getTrafficElement(String.valueOf(ThreadLocalRandom.current().nextInt(0, 300))); +// } +// +// private String getTrafficElement(String idSpiral) throws IOException +// { +// String template = readFromInputStream("/traffic-flow-observed-template.json"); +// +// Date dateObserved = new Date(); +// SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm"); +// formatter.setTimeZone(TimeZone.getTimeZone("UTC")); +// String strDateObserved = formatter.format(dateObserved); +// String dateObservedFormatted = strDateObserved.replace("/","").replace(":","").replace(" ",""); +// +// DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); +// df.setTimeZone(TimeZone.getTimeZone("UTC")); +// template = template.replace("#dateObserved#",df.format(dateObserved)); +// template = template.replace("#dateObservedFormat#", dateObservedFormatted); +// template = template.replace("#intensity#", String.valueOf(ThreadLocalRandom.current().nextInt(0, 300))); +// template = template.replace("#id_spiral#",idSpiral); +// return template; +// } +// +// private HttpResponse buildPost(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// HttpPost post = new HttpPost( INSERT_URI); +// post.addHeader("content-type", "application/json"); +// post.setEntity(new StringEntity(data)); +// +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( post ); +// } +// +// private HttpResponse buildPut(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// String id = JsonParser.parseString(data).getAsJsonObject().get("id").getAsString(); +// HttpPut put = new HttpPut( UPDATE_URI + "/" + URLEncoder.encode(id, StandardCharsets.UTF_8.toString())); +// put.addHeader("content-type", "application/json"); +// put.setEntity(new StringEntity(data)); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( put ); +// } +// +// private HttpResponse buildDelete(String data) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// String id = JsonParser.parseString(data).getAsJsonObject().get("id").getAsString(); +// HttpDelete delete = new HttpDelete( DELETE_URI + "/" + URLEncoder.encode(id, StandardCharsets.UTF_8.toString())); +// delete.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( delete ); +// } +// +// private HttpResponse buildQuery(Date start, Date end, AggregatorEnum aggregatorEnum, String tags) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Conversion +// SimpleDateFormat simpleDateFormat = new SimpleDateFormat( +// "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); +// simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); +// +// String startDate = simpleDateFormat.format(start); +// String endDate = simpleDateFormat.format(end); +// +// String query = String.format(AGGREGATE_URI + "/intensity?startDate=%s&endDate=%s&aggregator=%s&tags=%s",startDate, endDate, aggregatorEnum, URLEncoder.encode(tags, StandardCharsets.UTF_8.toString())); +// HttpGet get = new HttpGet(query); +// get.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( get ); +// } +// +// private HttpResponse buildQuery(Date start, Date end,String downsample, AggregatorEnum aggregatorEnum ) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Conversion +// SimpleDateFormat simpleDateFormat = new SimpleDateFormat( +// "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); +// simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); +// +// String startDate = simpleDateFormat.format(start); +// String endDate = simpleDateFormat.format(end); +// +// String query = String.format(AGGREGATE_URI + "/intensity?startDate=%s&endDate=%s&downsample=%s&aggregator=%s",startDate, endDate,downsample, aggregatorEnum); +// HttpGet get = new HttpGet(query); +// get.addHeader("content-type", "application/json"); +// return HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build()) +// .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build().execute( get ); +// } +// +// private String readFromInputStream(String path) +// throws IOException { +// InputStream inputStream = this.getClass().getResourceAsStream(path); +// StringBuilder resultStringBuilder = new StringBuilder(); +// try (BufferedReader br +// = new BufferedReader(new InputStreamReader(inputStream))) { +// String line; +// while ((line = br.readLine()) != null) { +// resultStringBuilder.append(line).append("\n"); +// } +// } +// return resultStringBuilder.toString(); +// } +// +// private void assertIntensity(String trafficElement) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// // Lo consulto +// Date start = Date.from(new Date().toInstant().minus(Duration.ofMinutes(5))); +// String id_spiral = JsonParser.parseString(trafficElement).getAsJsonObject().get("name").toString(); +// HttpResponse aggregatorResponse = this.buildQuery(start, new Date(), AggregatorEnum.SUM, "{\"id_spiral\":\"" + id_spiral +"\"}"); +// String result = EntityUtils.toString(aggregatorResponse.getEntity()); +// +// int intensityExpected = Integer.parseInt(JsonParser.parseString(trafficElement).getAsJsonObject().get("intensity").toString()); +// String dateObservedRecovery = JsonParser.parseString(trafficElement).getAsJsonObject().get("dateObserved").toString().replace("\"",""); +// String dateObservedEpoch = String.valueOf(Utils.ISO2Date(dateObservedRecovery).getTime()/1000); +// int intensityActual = JsonParser.parseString(result).getAsJsonArray().get(0).getAsJsonObject().get("dps").getAsJsonObject().get(dateObservedEpoch).getAsInt(); +// +// Assertions.assertEquals(intensityExpected, intensityActual); +// } +// +// private void assertSumIntensity(Date start, Date end, int sumIntensityExpected) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { +// end = Date.from(end.toInstant().plus(Duration.ofMinutes(1))); +// HttpResponse aggregatorResponse = this.buildQuery(start, end, "all-sum", AggregatorEnum.SUM); +// String result = EntityUtils.toString(aggregatorResponse.getEntity()); +// +// String startEpoch = String.valueOf(start.getTime()/1000); +// int sumIntensityActual = JsonParser.parseString(result).getAsJsonArray().get(0).getAsJsonObject().get("dps").getAsJsonObject().get(startEpoch).getAsInt(); +// Assertions.assertEquals(sumIntensityExpected, sumIntensityActual); +// } +// +// private String modifyIntensity(String trafficElement) +// { +// JsonObject jsonObject1 = JsonParser.parseString(trafficElement).getAsJsonObject(); +// jsonObject1.remove("intensity"); +// int newIntensity = ThreadLocalRandom.current().nextInt(0, 300); +// jsonObject1.addProperty("intensity", newIntensity); +// trafficElement = jsonObject1.toString(); +// return trafficElement; +// } +//} +// diff --git a/openDataRetrieval/shared/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java b/openDataRetrieval/shared/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java new file mode 100644 index 0000000000000000000000000000000000000000..09f6ff2dbffa077b73c6e2d2c6cb78b0d375d12a --- /dev/null +++ b/openDataRetrieval/shared/src/test/java/com/tecnalia/urbanite/storage/tools/Export.java @@ -0,0 +1,133 @@ +package com.tecnalia.urbanite.storage.tools; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.tecnalia.urbanite.storage.Utils.Utils; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +/** + * export the trafficflowobserved from the database mongo db to database opentsdb. + */ +public class Export { + + private static final String GET_URL = "https://bilbao.urbanite.esilab.org/data/getTDataRange/trafficFlowObserved/bilbao"; + + public static void main(String[] args) throws IOException + { + LocalDateTime start = LocalDateTime.parse("2021-01-01T00:00:00.000Z",DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + LocalDateTime end = LocalDateTime.parse("2021-12-31T00:00:00.000Z",DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + File file = createFile("C:\\FicherosUrbanite\\traffic-2021.txt"); + BufferedWriter writer = new BufferedWriter(new FileWriter(file, true)); + + File errorsFile = createFile("C:\\FicherosUrbanite\\errors.txt"); + BufferedWriter errorWriter = new BufferedWriter(new FileWriter(errorsFile, true)); + for (LocalDateTime iterationDate = start; iterationDate.isBefore(end); iterationDate = iterationDate.plusDays(1)) + { + System.out.println(iterationDate); + Map<String,String> map = new HashMap<>(); + + try + { + + String queryStart = iterationDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + LocalDateTime queryEndDatetime = iterationDate.plusHours(23).plusMinutes(59).plusSeconds(59).plusNanos(999); + String queryEnd = queryEndDatetime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")); + String nameForFile = queryStart.replace("-","").replace("-","").replace(":","").replace(".",""); + + + + HttpGet get = new HttpGet(String.format(GET_URL + "?startDate=%s&endDate=%s&limit=100000&sort=ASC", URLEncoder.encode(queryStart, StandardCharsets.UTF_8.toString()), URLEncoder.encode(queryEnd, StandardCharsets.UTF_8.toString()))); + get.addHeader("content-type", "application/json"); + HttpResponse response = HttpClientBuilder.create().build().execute( get ); + if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) + { + String json = EntityUtils.toString(response.getEntity()); + + if ( json != null && !json.isEmpty()) + { + JsonArray array = JsonParser.parseString(json).getAsJsonArray(); + + if (array != null && array.size() > 0) + { + if ( file != null) + { + + for (JsonElement element : array) + { + String dateObserved = element.getAsJsonObject().get("dateObserved").getAsString(); + String idSpiral = element.getAsJsonObject().get("name").getAsString(); + String intensity = element.getAsJsonObject().get("intensity").getAsString(); + + String key = dateObserved+idSpiral; + if ( !map.containsKey(key)) + { + writer.write(String.format("trafficflowobserved.intensity %s %s city=bilbao id_spiral=%s\n",Utils.ISO2Date(dateObserved).getTime()/1000, intensity, idSpiral)); + map.put(key, intensity); + } + } + + }else + { + errorWriter.write("Date:" + start + ". It could not create the file."); + } + } + else + { + errorWriter.write("Date:" + start + ". The array from the get response is null or empty."); + } + }else + { + errorWriter.write("Date:" + start + ". The entity is null or empty."); + } + } + else + { + errorWriter.write("Date:" + start + ". The response is null or empty."); + } + } + catch (Exception ex) + { + try { + errorWriter.write("Date:" + start + ". It ocurred an exception. " + ex.getMessage()); + + } + catch (Exception e) + { + System.out.println("Error!!"); + } + + System.out.println("Error!!"); + } + } + errorWriter.close(); + writer.close(); + } + + + private static File createFile(String name) throws IOException + { + File file = new File(name); + if ( file.exists()) + { + file.delete(); + } + + return file.createNewFile() != false ? file : null; + } +} diff --git a/openDataRetrieval/shared/src/test/resources/traffic-flow-observed-template.json b/openDataRetrieval/shared/src/test/resources/traffic-flow-observed-template.json new file mode 100644 index 0000000000000000000000000000000000000000..b831cc245ae5a9b1d71b0d5d7333200aef0f1d8b --- /dev/null +++ b/openDataRetrieval/shared/src/test/resources/traffic-flow-observed-template.json @@ -0,0 +1,18 @@ +{ + "@context" : [ "https://smartdatamodels.org/context.jsonld", "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ], + "address" : { + "addressCountry" : "ES", + "addressLocality" : "Bilbao" + }, + "type" : "TrafficFlowObserved", + "location" : { + "coordinates" : [ [ [ 504417.9371092392, 4790030.564573327 ], [ 504453.27293873084, 4790030.564573327 ], [ 504453.27293873084, 4790133.21077546 ], [ 504417.9371092392, 4790133.21077546 ] ] ], + "type" : "Polygon" + }, + "dateObserved" : "#dateObserved#", + "occupancy" : 1, + "intensity" : #intensity#, + "averageVehicleSpeed" : 54, + "name" : #id_spiral#, + "id" : "urn:ngsi-ld:TrafficFlowObserved:#id_spiral#:#dateObservedFormat#" +} \ No newline at end of file diff --git a/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/OpenDataRetrievalApplication.java b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/OpenDataRetrievalApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..65750a8b09eba6453dcb0675158f3c4a3a2e5585 --- /dev/null +++ b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/OpenDataRetrievalApplication.java @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.retrieval; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class OpenDataRetrievalApplication { + + public static void main(String[] args) + { + SpringApplication.run(OpenDataRetrievalApplication.class, args); + } + + @Bean + public OpenAPI customOpenAPI(@Value("${application-description}") String appDesciption, @Value("${application-version}") String appVersion) { + return new OpenAPI() + .addServersItem(new Server().url("/data/")) + .info(new Info() + .title("Open Data Retrieval Component API") + .version(appVersion) + .description(appDesciption) + ); + } + +} diff --git a/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/OpenRetrievalAPI.java b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/OpenRetrievalAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..5299dfe5709e6fc953a843c61c65af8edf5a85bc --- /dev/null +++ b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/OpenRetrievalAPI.java @@ -0,0 +1,454 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.retrieval; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.tecnalia.urbanite.storage.APIResponse; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import com.tecnalia.urbanite.storage.DataModel.SortingMode; +import com.tecnalia.urbanite.storage.Utils.Utils; +import com.tecnalia.urbanite.storage.controllers.TrafficFlowObservedController; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@RestController +@Tag(name = "Open Data Retrieval", description = "Operations to retrieve open data") +public class OpenRetrievalAPI { + + private Logger logger = LoggerFactory.getLogger(OpenRetrievalAPI.class); + + @ApiResponse(responseCode = "200", description = "Successful operation.") + @Operation(summary = "Get available Data Models", description = "Returns all the Data Models that are currently implemented.") + @RequestMapping( path = "/getSupportedDataModels", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getSupportedDataModels() { + + List<JSONObject> lstRes = new ArrayList<JSONObject>(); + for (DataModel m: DataModel.values()){ + JSONObject mod = new JSONObject(); + try { + mod.put("id", m.getId()); + mod.put("name", m.getName()); + mod.put("description", m.getDescription()); + mod.put("reference", m.getReference()); + mod.put("example", m.getExample()); + lstRes.add(mod); + } catch (JSONException e) { + logger.error("RetrievalAPI::getDataModels::. Error creating JSON with model " + m.getName() + ": " + e.getMessage()); + } + } + APIResponse res = new APIResponse(HttpStatus.OK, lstRes); + return Utils.formatResponse(res, true); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Get data from the database within a specific time range", description = "Returns data of type {model} from the database of the city {city}, according to the specified time range,") + @RequestMapping( path = "/getTDataRange/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getTDataRange( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + res = model.getTDataRange(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request."), + @ApiResponse(responseCode = "404", description = "Document not found.")}) + @Operation(summary = "Get a record from the database", description = "Returns the record of type {model} identified by {id} from the database of the city {city}.") + @RequestMapping( path = "/getTData/{model}/{city}/{id}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getDataById( + @PathVariable DataModel model, + @PathVariable City city, + @PathVariable String id) { + + APIResponse res = model.getDataByID(city, id); + return Utils.formatResponse(res); + } + + @Operation(summary = "Get data from the database", description = "Returns data of type {model} from the database of the city {city}. Some filters can be applied to model fields.") + @RequestMapping( + path = "/getTData/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getTData( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description = "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + //check the filters are in JSON format + //System.out.println("filters: " + filters); + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + res = model.getTData(city, jsFilters, lstReturnFields, limit, sort); + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + + return Utils.formatResponse(res, true); + } + + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successful operation."), + @ApiResponse(responseCode = "400", description = "Bad request.")}) + @Operation(summary = "Get the different values for a specific field.", description = "Returns the different values of the field of type {model} from the database of the city {city}.") + @RequestMapping( path = "/getDistinct/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> getDistinct( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description = "Data model field to return its different values.") @RequestParam(required = false) String field) { + + APIResponse res = new APIResponse(); + + if (field.trim().isEmpty()) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Field name can't be empty."); + } + else if (!field.contains(",")){ + res = model.getDistinct(city, field); + } + else { + res = model.getDistinct(city, field.split(",")); + } + + return Utils.formatResponse(res, false); + } + + + + @Hidden + @RequestMapping( path = "/testNewGetTDataRange/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> testNewGetTDataRange( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + + TrafficFlowObservedController cont = new TrafficFlowObservedController(); + res = cont.getTDataRangeOtherMethod(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } + + + + @Hidden + @RequestMapping( path = "/testNewGetTDataRangeOther/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> testNewGetTDataRangeOtherOther( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + + TrafficFlowObservedController cont = new TrafficFlowObservedController(); + res = cont.getTDataRangeOtherMethod2(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } + + + @Hidden + @RequestMapping( path = "/testGetTDataRangeThreads/{model}/{city}", + method = RequestMethod.GET, + produces = {"application/json"}) + public ResponseEntity<Object> testGetTDataRangeThreads( + @PathVariable DataModel model, + @PathVariable City city, + @Parameter(description= "Date and time (ISO8601 UTC format) from which to get the data. Mandatory if \"endDate\" is not present.", example="2021-02-15T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate, + @Parameter(description= "Date and time (ISO8601 UTC format) until which to get the data. Mandatory if \"startDate\" is not present.", example="2021-02-16T00:00:00.000Z") @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate, + @Parameter(description= "Different filters (Data Model fields) to apply, in JSON format.") @RequestParam(required = false) String filters, + @Parameter(description= "Comma separated list of names (text) of Data Model fields to be returned. If not set, all fields will be returned.") @RequestParam(required = false) String returnFields, + @Parameter(description= "Number or documents to retrieve.") @RequestParam(required = false, defaultValue = "1000") Integer limit, + @Parameter(description= "Sort results by date in ascending (oldest first) or descending (newest first - default) order.") @RequestParam(required = false) SortingMode sort) { + + APIResponse res = new APIResponse(); + + if (sort == null) sort = SortingMode.DESC; + if (filters == null) filters = "{}"; + if (returnFields == null) returnFields = ""; + + boolean datesOk = true; + if ((startDate == null) && (endDate == null)) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("No time range specified. 'startDate' and/or 'endDate' must be indicated."); + datesOk = false; + } + else { + if ((startDate != null) && (endDate != null)) { + if (startDate.compareTo(endDate) > 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("'endDate' cann't be before 'startDate'."); + datesOk = false; + } + } + } + + if (datesOk) { + //check the filters are in JSON format + try { + JSONObject jsFilters = new JSONObject(filters); + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + if (limit <= 0) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Number of documents must be greater than zero."); + } + else { + List<String> lstReturnFields= new ArrayList<String>(); + if (returnFields.isEmpty() == false) { + String[] fields = returnFields.split(","); + for (String f: fields) + lstReturnFields.add(f.trim()); + } + + TrafficFlowObservedController cont = new TrafficFlowObservedController(); + res = cont.getTDataRangeThreadsMethod(city, startDate, endDate, jsFilters, lstReturnFields, limit, sort); + } + } + } catch (JSONException e) { + res.setStatus(HttpStatus.BAD_REQUEST); + res.setError("Filters are not in JSON format"); + } + } + + return Utils.formatResponse(res, true); + } +} diff --git a/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/ValidateAccessHandler.java b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/ValidateAccessHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..b37e22d1666eeb6ef1c1e5154ca08183522b31f1 --- /dev/null +++ b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/ValidateAccessHandler.java @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.retrieval; + +import com.tecnalia.urbanite.storage.DataModel.City; +import com.tecnalia.urbanite.storage.DataModel.DataModel; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.HandlerMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +@Component +public class ValidateAccessHandler implements HandlerInterceptor +{ + final static HashMap<DataModel, EnumSet<City>>privateModels; + + static + { + privateModels = new HashMap<>(); + privateModels.put(DataModel.trafficFlowObserved, EnumSet.of(City.amsterdam, City.messina)); + privateModels.put(DataModel.censusObserved, EnumSet.of(City.bilbao, City.helsinki,City.messina, City.amsterdam)); + privateModels.put(DataModel.touristTrip, EnumSet.of(City.amsterdam)); + privateModels.put(DataModel.originDestinationMatrix, EnumSet.of(City.bilbao, City.helsinki,City.messina, City.amsterdam)); + privateModels.put(DataModel.mapLayer, EnumSet.of(City.bilbao, City.helsinki,City.messina, City.amsterdam)); + privateModels.put(DataModel.vehicle, EnumSet.of(City.messina)); + + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + { + @SuppressWarnings("unchecked") + final Map<String, String> paths = (Map<String, String>) request .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + + if ( paths != null && !paths.isEmpty()) + { + String model = paths.get("model"); + String city = paths.get("city"); + + if ( model != null && city != null && DataModel.any(model) && City.any(city)) + { + + EnumSet<City> privateCities = privateModels.get(DataModel.valueOf(model)); + + if ( privateCities != null && !privateCities.isEmpty()) + { + boolean isForbidden = privateCities.contains(City.valueOf(city)); + + if ( isForbidden ) + { + //response.getWriter().write("something"); + response.setStatus(HttpStatus.FORBIDDEN.value()); + return false; + } + } + } + } + + return true; + } +} diff --git a/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/WebConfig.java b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/WebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..3e09c7fd3bfb0176e4a9e50eb3a0f40aa74d2204 --- /dev/null +++ b/openDataRetrieval/src/main/java/com/tecnalia/urbanite/retrieval/WebConfig.java @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.retrieval; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Autowired + ValidateAccessHandler validateAccessHandler; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(validateAccessHandler); + } +} \ No newline at end of file diff --git a/openDataRetrieval/src/main/resources/application.properties b/openDataRetrieval/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..bb9e624a29fbb7bd89e141e36de89c31b631ded1 --- /dev/null +++ b/openDataRetrieval/src/main/resources/application.properties @@ -0,0 +1,6 @@ +application-description=@project.description@ +application-version=@project.version@ +logging.level.org.springframework.boot.autoconfigure=ERROR +server.servlet.context-path=/data +server.port=80 +logging.level.com.tecnalia.urbanite.storage=DEBUG \ No newline at end of file diff --git a/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/ValidateAccessHandlerTest.java b/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/ValidateAccessHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0578a9de88054573e24615d5499f5ed09f73eb4c --- /dev/null +++ b/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/ValidateAccessHandlerTest.java @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.retrieval; + +import com.tecnalia.urbanite.retrieval.entities.RequestEntity; +import com.tecnalia.urbanite.retrieval.entities.ResponseEntity; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +public class ValidateAccessHandlerTest +{ + final private ValidateAccessHandler validateAccessHandler = new ValidateAccessHandler(); + + @ParameterizedTest + @MethodSource("loadTestBattery") + void demoTestMethod(String model, String city, boolean expected) + { + + boolean actual = validateAccessHandler.preHandle(new RequestEntity(model,city), new ResponseEntity(), null); + assertEquals(actual, expected); + } + + + private static Stream<Arguments> loadTestBattery() { + return Stream.of( + arguments(null, null, true), //null + arguments(null, "amsterdam", true), //empty + arguments("trafficFlowObserved", "", true), //empty + arguments("", "amsterdam", true),//empty + arguments("trafficFlowObserved", "", true), //empty + arguments("trafficflowobserved", "bilbao", true), //lowercase tiene que pasar aunque luego en el GET no encontrarĂ¡ el enum + arguments("modelNotExist", "bilbao", true), //tiene que pasar aunque luego en el GET no encontrarĂ¡ el enum + arguments("trafficFlowObserved", "amsterdam", false), + arguments("trafficFlowObserved", "bilbao", true), + arguments("censusObserved", "bilbao", false), + arguments("censusObserved", "helsinki", false), + arguments("censusObserved", "messina", false), + arguments("censusObserved", "amsterdam", false), + arguments("touristTrip", "amsterdam", false), + arguments("touristTrip", "bilbao", true), + arguments("originDestinationMatrix", "bilbao", false), + arguments("mapLayer", "helsinki", false), + arguments("airQualityObserved", "bilbao", true), + arguments("airQualityObserved", "helsinki", true), + arguments("airQualityObserved", "messina", true), + arguments("airQualityObserved", "amsterdam", true) + ); + } + +} diff --git a/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/entities/RequestEntity.java b/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/entities/RequestEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..f402d2cdb14ea02fecaf477ffc64a401ed1d6863 --- /dev/null +++ b/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/entities/RequestEntity.java @@ -0,0 +1,385 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.retrieval.entities; + + +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.*; + +public class RequestEntity implements HttpServletRequest +{ + final private String model; + final private String city; + + public RequestEntity(String model, String city) + { + this.model = model; + this.city = city; + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public Cookie[] getCookies() { + return new Cookie[0]; + } + + @Override + public long getDateHeader(String s) { + return 0; + } + + @Override + public String getHeader(String s) { + return null; + } + + @Override + public Enumeration<String> getHeaders(String s) { + return null; + } + + @Override + public Enumeration<String> getHeaderNames() { + return null; + } + + @Override + public int getIntHeader(String s) { + return 0; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public String getPathInfo() { + return null; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getContextPath() { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public boolean isUserInRole(String s) { + return false; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public String getRequestedSessionId() { + return null; + } + + @Override + public String getRequestURI() { + return null; + } + + @Override + public StringBuffer getRequestURL() { + return null; + } + + @Override + public String getServletPath() { + return null; + } + + @Override + public HttpSession getSession(boolean b) { + return null; + } + + @Override + public HttpSession getSession() { + return null; + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean authenticate(HttpServletResponse httpServletResponse){ + return false; + } + + @Override + public void login(String s, String s1) { + + } + + @Override + public void logout() { + + } + + @Override + public Collection<Part> getParts() { + return null; + } + + @Override + public Part getPart(String s){ + return null; + } + + @Override + public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) { + return null; + } + + @Override + public Object getAttribute(String s) { + HashMap<String,String> paths = new HashMap<>(); + paths.put("model",this.model); + paths.put("city",this.city); + return paths; + } + + @Override + public Enumeration<String> getAttributeNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public void setCharacterEncoding(String s) { + + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public long getContentLengthLong() { + return 0; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletInputStream getInputStream() { + return null; + } + + @Override + public String getParameter(String s) { + return null; + } + + @Override + public Enumeration<String> getParameterNames() { + return null; + } + + @Override + public String[] getParameterValues(String s) { + return new String[0]; + } + + @Override + public Map<String, String[]> getParameterMap() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public String getScheme() { + return null; + } + + @Override + public String getServerName() { + return null; + } + + @Override + public int getServerPort() { + return 0; + } + + @Override + public BufferedReader getReader() { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public void setAttribute(String s, Object o) { + + } + + @Override + public void removeAttribute(String s) { + + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public Enumeration<Locale> getLocales() { + return null; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public RequestDispatcher getRequestDispatcher(String s) { + return null; + } + + @Override + public String getRealPath(String s) { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public String getLocalAddr() { + return null; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } +} \ No newline at end of file diff --git a/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/entities/ResponseEntity.java b/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/entities/ResponseEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..afd1a09753bb92a7b1bbef4f209afc62f68aeefc --- /dev/null +++ b/openDataRetrieval/src/test/java/com/tecnalia/urbanite/retrieval/entities/ResponseEntity.java @@ -0,0 +1,213 @@ +/* +* Copyright (c) 2022 TECNALIA RESEARCH & INNOVATION. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of Affero General Public License (AGPL) version 3 +* which accompanies this distribution, and is available at + +* https://www.gnu.org/licenses/agpl-3.0.en.html +* +* Contributors: +* +* Gonzalo Lazaro, Jose Manuel Rio, Alejandro Rodriguez, Sonia Bilbao from Tecnalia +* +* Initially developed in the context of URBANITE EU project +* www.urbanite-project.eu +*/ +package com.tecnalia.urbanite.retrieval.entities; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Locale; + +public class ResponseEntity implements HttpServletResponse +{ + + @Override + public void addCookie(Cookie cookie) { + + } + + @Override + public boolean containsHeader(String s) { + return false; + } + + @Override + public String encodeURL(String s) { + return null; + } + + @Override + public String encodeRedirectURL(String s) { + return null; + } + + @Override + public String encodeUrl(String s) { + return null; + } + + @Override + public String encodeRedirectUrl(String s) { + return null; + } + + @Override + public void sendError(int i, String s) { + + } + + @Override + public void sendError(int i) { + + } + + @Override + public void sendRedirect(String s) { + + } + + @Override + public void setDateHeader(String s, long l) { + + } + + @Override + public void addDateHeader(String s, long l) { + + } + + @Override + public void setHeader(String s, String s1) { + + } + + @Override + public void addHeader(String s, String s1) { + + } + + @Override + public void setIntHeader(String s, int i) { + + } + + @Override + public void addIntHeader(String s, int i) { + + } + + @Override + public void setStatus(int i) { + + } + + @Override + public void setStatus(int i, String s) { + + } + + @Override + public int getStatus() { + return 0; + } + + @Override + public String getHeader(String s) { + return null; + } + + @Override + public Collection<String> getHeaders(String s) { + return null; + } + + @Override + public Collection<String> getHeaderNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletOutputStream getOutputStream() { + return null; + } + + @Override + public PrintWriter getWriter() { + return null; + } + + @Override + public void setCharacterEncoding(String s) { + + } + + @Override + public void setContentLength(int i) { + + } + + @Override + public void setContentLengthLong(long l) { + + } + + @Override + public void setContentType(String s) { + + } + + @Override + public void setBufferSize(int i) { + + } + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public void flushBuffer() { + + } + + @Override + public void resetBuffer() { + + } + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() { + + } + + @Override + public void setLocale(Locale locale) { + + } + + @Override + public Locale getLocale() { + return null; + } +} \ No newline at end of file