diff --git a/DEPENDENCIES b/DEPENDENCIES index 2958fd96b..5b35bcf25 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,26 +1,26 @@ -maven/mavencentral/ch.qos.logback/logback-classic/1.4.14, EPL-1.0 OR LGPL-2.1-only, approved, #3435 -maven/mavencentral/ch.qos.logback/logback-core/1.4.14, EPL-1.0 OR LGPL-2.1-only, approved, #3373 -maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.4, Apache-2.0, approved, #7947 -maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.4, MIT AND Apache-2.0, approved, #7932 -maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.4, Apache-2.0, approved, #7934 -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-toml/2.15.4, Apache-2.0, approved, #9160 -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.4, Apache-2.0, approved, #8802 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.15.4, Apache-2.0, approved, #8808 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.4, Apache-2.0, approved, #7930 +maven/mavencentral/ch.qos.logback/logback-classic/1.4.14, EPL-1.0 AND LGPL-2.1-only, approved, #15230 +maven/mavencentral/ch.qos.logback/logback-core/1.4.14, EPL-1.0 AND LGPL-2.1-only, approved, #15209 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.4, Apache-2.0, approved, #15260 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.4, , approved, #15194 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.4, Apache-2.0, approved, #15199 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-toml/2.15.4, Apache-2.0, approved, #15242 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.4, Apache-2.0, approved, #15207 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.15.4, Apache-2.0, approved, #15281 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.4, Apache-2.0, approved, #15189 maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-base/2.15.4, Apache-2.0, approved, #9235 maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.15.4, Apache-2.0, approved, #9236 maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.15.4, Apache-2.0, approved, #9241 maven/mavencentral/com.fasterxml.jackson.module/jackson-module-kotlin/2.15.4, Apache-2.0, approved, #10051 -maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.15.4, Apache-2.0, approved, #8803 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.15.4, Apache-2.0, approved, #15219 maven/mavencentral/com.fasterxml/classmate/1.6.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.dasniko/testcontainers-keycloak/3.2.0, Apache-2.0, approved, #14719 maven/mavencentral/com.github.docker-java/docker-java-api/3.3.6, Apache-2.0, approved, #10346 -maven/mavencentral/com.github.docker-java/docker-java-transport-zerodep/3.3.6, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #7946 +maven/mavencentral/com.github.docker-java/docker-java-transport-zerodep/3.3.6, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #15251 maven/mavencentral/com.github.docker-java/docker-java-transport/3.3.6, Apache-2.0, approved, #7942 -maven/mavencentral/com.github.java-json-tools/btf/1.3, Apache-2.0 OR LGPL-3.0-or-later, approved, #2721 -maven/mavencentral/com.github.java-json-tools/jackson-coreutils/2.0, Apache-2.0 OR LGPL-3.0-or-later, approved, #2719 +maven/mavencentral/com.github.java-json-tools/btf/1.3, Apache-2.0 AND GPL-1.0-or-later AND LGPL-3.0-only AND Apache-2.0 AND LGPL-3.0-only, restricted, #15201 +maven/mavencentral/com.github.java-json-tools/jackson-coreutils/2.0, Apache-2.0 OR LGPL-3.0-or-later, approved, #15186 maven/mavencentral/com.github.java-json-tools/json-patch/1.13, Apache-2.0 OR LGPL-3.0-or-later, approved, CQ23929 -maven/mavencentral/com.github.java-json-tools/msg-simple/1.2, Apache-2.0 OR LGPL-3.0-or-later, approved, #2720 +maven/mavencentral/com.github.java-json-tools/msg-simple/1.2, Apache-2.0 AND LGPL-2.1-or-later AND LGPL-3.0-only AND (Apache-2.0 AND GPL-1.0-or-later AND LGPL-3.0-only) AND Apache-2.0 AND LGPL-3.0-only, restricted, #15239 maven/mavencentral/com.github.stephenc.jcip/jcip-annotations/1.0-1, Apache-2.0, approved, CQ21949 maven/mavencentral/com.github.tomakehurst/wiremock-jre8-standalone/2.35.2, MIT AND Apache-2.0, approved, #6979 maven/mavencentral/com.google.code.gson/gson/2.10.1, Apache-2.0, approved, #6159 @@ -32,13 +32,16 @@ maven/mavencentral/com.nimbusds/lang-tag/1.7, Apache-2.0, approved, clearlydefin maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.37.2, Apache-2.0, approved, #11701 maven/mavencentral/com.nimbusds/oauth2-oidc-sdk/9.43.3, Apache-2.0, approved, clearlydefined maven/mavencentral/com.ninja-squad/springmockk/4.0.2, Apache-2.0, approved, clearlydefined -maven/mavencentral/com.sun.istack/istack-commons-runtime/4.1.2, BSD-3-Clause, approved, #2590 +maven/mavencentral/com.opencsv/opencsv/5.9, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.sun.istack/istack-commons-runtime/4.1.2, BSD-3-Clause, approved, #15290 maven/mavencentral/com.sun.istack/istack-commons-tools/4.1.2, BSD-3-Clause, approved, #2580 maven/mavencentral/com.sun.xml.bind.external/relaxng-datatype/4.0.2, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/com.sun.xml.bind.external/rngom/4.0.2, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/com.vaadin.external.google/android-json/0.0.20131108.vaadin1, Apache-2.0, approved, CQ21310 maven/mavencentral/com.zaxxer/HikariCP/5.0.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/commons-beanutils/commons-beanutils/1.9.4, Apache-2.0, approved, CQ12654 maven/mavencentral/commons-codec/commons-codec/1.16.1, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #9157 +maven/mavencentral/commons-collections/commons-collections/3.2.2, Apache-2.0, approved, #15185 maven/mavencentral/commons-io/commons-io/2.11.0, Apache-2.0, approved, CQ23745 maven/mavencentral/commons-io/commons-io/2.16.1, Apache-2.0, approved, #14190 maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162 @@ -70,7 +73,7 @@ maven/mavencentral/io.netty/netty-transport-native-epoll/4.1.109.Final, Apache-2 maven/mavencentral/io.netty/netty-transport-native-unix-common/4.1.109.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 maven/mavencentral/io.netty/netty-transport/4.1.109.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 maven/mavencentral/io.projectreactor.netty/reactor-netty-core/1.1.18, Apache-2.0, approved, #5946 -maven/mavencentral/io.projectreactor.netty/reactor-netty-http/1.1.18, Apache-2.0, approved, #6999 +maven/mavencentral/io.projectreactor.netty/reactor-netty-http/1.1.20, Apache-2.0, approved, #6999 maven/mavencentral/io.projectreactor/reactor-core/3.6.5, Apache-2.0, approved, #13392 maven/mavencentral/io.quarkus/quarkus-junit4-mock/3.2.0.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/io.smallrye/jandex/3.1.2, Apache-2.0, approved, clearlydefined @@ -82,6 +85,7 @@ maven/mavencentral/jakarta.annotation/jakarta.annotation-api/2.1.1, EPL-2.0 OR G maven/mavencentral/jakarta.inject/jakarta.inject-api/2.0.1, Apache-2.0, approved, ee4j.cdi maven/mavencentral/jakarta.mail/jakarta.mail-api/2.1.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.mail maven/mavencentral/jakarta.persistence/jakarta.persistence-api/3.1.0, EPL-2.0 OR BSD-3-Clause, approved, ee4j.jpa +maven/mavencentral/jakarta.persistence/jakarta.persistence-api/3.2.0, EPL-2.0 OR BSD-3-Clause, approved, ee4j.jpa maven/mavencentral/jakarta.transaction/jakarta.transaction-api/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jta maven/mavencentral/jakarta.validation/jakarta.validation-api/3.0.2, Apache-2.0, approved, ee4j.validation maven/mavencentral/jakarta.ws.rs/jakarta.ws.rs-api/3.1.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.rest @@ -89,37 +93,39 @@ maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/4.0.2, BSD-3-Clause, ap maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636 maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.14.13, Apache-2.0, approved, #7164 maven/mavencentral/net.bytebuddy/byte-buddy/1.14.13, Apache-2.0 AND BSD-3-Clause, approved, #7163 -maven/mavencentral/net.java.dev.jna/jna/5.13.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #6709 +maven/mavencentral/net.java.dev.jna/jna/5.13.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #15196 maven/mavencentral/net.minidev/accessors-smart/2.5.1, Apache-2.0, approved, clearlydefined maven/mavencentral/net.minidev/json-smart/2.5.1, Apache-2.0, approved, clearlydefined maven/mavencentral/org.antlr/antlr4-runtime/4.13.0, BSD-3-Clause, approved, #10767 +maven/mavencentral/org.apache.commons/commons-collections4/4.4, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.commons/commons-compress/1.24.0, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, #10368 maven/mavencentral/org.apache.commons/commons-csv/1.11.0, Apache-2.0, approved, #14690 maven/mavencentral/org.apache.commons/commons-lang3/3.13.0, Apache-2.0, approved, #9820 -maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.14, Apache-2.0 AND LicenseRef-Public-Domain, approved, CQ23527 +maven/mavencentral/org.apache.commons/commons-text/1.11.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.14, Apache-2.0, approved, #15248 maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.16, Apache-2.0, approved, CQ23528 maven/mavencentral/org.apache.james/apache-mime4j-core/0.8.9, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.james/apache-mime4j-dom/0.8.9, Apache-2.0, approved, #2340 maven/mavencentral/org.apache.james/apache-mime4j-storage/0.8.9, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.logging.log4j/log4j-api/2.21.1, Apache-2.0 AND (Apache-2.0 AND LGPL-2.0-or-later), approved, #11079 -maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.21.1, Apache-2.0, approved, #11919 +maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.21.1, Apache-2.0, approved, #15262 maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.20, Apache-2.0 AND (EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0) AND (CDDL-1.0 OR GPL-2.0-only WITH Classpath-exception-2.0) AND W3C AND CC0-1.0, approved, #5949 maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.20, Apache-2.0, approved, #6997 maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.20, Apache-2.0, approved, #7920 maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.aspectj/aspectjweaver/1.9.22, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #7695 +maven/mavencentral/org.aspectj/aspectjweaver/1.9.22, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #15252 maven/mavencentral/org.assertj/assertj-core/3.24.2, Apache-2.0, approved, #6161 maven/mavencentral/org.awaitility/awaitility/4.2.1, Apache-2.0, approved, #14178 maven/mavencentral/org.checkerframework/checker-qual/3.31.0, MIT, approved, clearlydefined maven/mavencentral/org.eclipse.angus/angus-activation/2.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus maven/mavencentral/org.eclipse.angus/angus-mail/2.0.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus -maven/mavencentral/org.eclipse.tractusx/bpdm-common-test/6.0.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-common/6.0.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-gate-api/6.0.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator-api/6.0.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator/6.0.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.eclipse.tractusx/bpdm-pool-api/6.0.1-SNAPSHOT, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.flywaydb/flyway-core/9.22.3, Apache-2.0, approved, #10349 +maven/mavencentral/org.eclipse.tractusx/bpdm-common-test/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-common/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-gate-api/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator-api/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-orchestrator/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.eclipse.tractusx/bpdm-pool-api/6.0.2-SNAPSHOT, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.flywaydb/flyway-core/9.22.3, Apache-2.0, approved, #15215 maven/mavencentral/org.glassfish.jaxb/codemodel/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/org.glassfish.jaxb/jaxb-core/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/org.glassfish.jaxb/jaxb-jxc/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl @@ -129,7 +135,7 @@ maven/mavencentral/org.glassfish.jaxb/txw2/4.0.5, BSD-3-Clause, approved, ee4j.j maven/mavencentral/org.glassfish.jaxb/xsom/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/org.hamcrest/hamcrest-core/2.2, BSD-3-Clause, approved, clearlydefined maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-3-Clause, approved, clearlydefined -maven/mavencentral/org.hdrhistogram/HdrHistogram/2.1.12, BSD-2-Clause OR LicenseRef-Public-Domain, approved, CQ13192 +maven/mavencentral/org.hdrhistogram/HdrHistogram/2.1.12, CC0-1.0, approved, #15259 maven/mavencentral/org.hibernate.common/hibernate-commons-annotations/6.0.6.Final, LGPL-2.1-only, approved, #6962 maven/mavencentral/org.hibernate.orm/hibernate-core/6.4.4.Final, LGPL-2.1-or-later AND (EPL-2.0 OR BSD-3-Clause) AND MIT, approved, #12490 maven/mavencentral/org.hibernate.validator/hibernate-validator/8.0.1.Final, Apache-2.0, approved, clearlydefined @@ -155,14 +161,14 @@ maven/mavencentral/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.3, Apache-2 maven/mavencentral/org.jetbrains/annotations/13.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.2, EPL-2.0, approved, #9714 maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.2, EPL-2.0, approved, #9711 -maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.2, EPL-2.0, approved, #9708 -maven/mavencentral/org.junit.jupiter/junit-jupiter/5.10.2, EPL-2.0, approved, #13393 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.2, EPL-2.0, approved, #15250 +maven/mavencentral/org.junit.jupiter/junit-jupiter/5.10.2, EPL-2.0, approved, #15197 maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.2, EPL-2.0, approved, #9715 maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.2, EPL-2.0, approved, #9709 maven/mavencentral/org.keycloak/keycloak-admin-client/23.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.keycloak/keycloak-common/23.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.keycloak/keycloak-core/23.0.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.latencyutils/LatencyUtils/2.0.3, BSD-2-Clause, approved, CQ17408 +maven/mavencentral/org.latencyutils/LatencyUtils/2.0.3, CC0-1.0, approved, #15280 maven/mavencentral/org.mockito/mockito-junit-jupiter/5.7.0, MIT, approved, #11423 maven/mavencentral/org.objenesis/objenesis/3.3, Apache-2.0, approved, clearlydefined maven/mavencentral/org.opentest4j/opentest4j/1.3.0, Apache-2.0, approved, #9713 @@ -199,8 +205,8 @@ maven/mavencentral/org.springframework.boot/spring-boot-starter/3.2.5, Apache-2. maven/mavencentral/org.springframework.boot/spring-boot-test-autoconfigure/3.2.5, Apache-2.0, approved, #12920 maven/mavencentral/org.springframework.boot/spring-boot-test/3.2.5, Apache-2.0, approved, #12916 maven/mavencentral/org.springframework.boot/spring-boot/3.2.5, Apache-2.0, approved, #11752 -maven/mavencentral/org.springframework.data/spring-data-commons/3.2.5, Apache-2.0, approved, #11917 -maven/mavencentral/org.springframework.data/spring-data-jpa/3.2.5, Apache-2.0, approved, #11882 +maven/mavencentral/org.springframework.data/spring-data-commons/3.2.5, Apache-2.0, approved, #15202 +maven/mavencentral/org.springframework.data/spring-data-jpa/3.2.5, Apache-2.0, approved, #15183 maven/mavencentral/org.springframework.security/spring-security-config/6.2.4, Apache-2.0, approved, #11896 maven/mavencentral/org.springframework.security/spring-security-core/6.2.4, Apache-2.0, approved, #11904 maven/mavencentral/org.springframework.security/spring-security-crypto/6.2.4, Apache-2.0 AND ISC, approved, #11908 @@ -210,20 +216,20 @@ maven/mavencentral/org.springframework.security/spring-security-oauth2-jose/6.2. maven/mavencentral/org.springframework.security/spring-security-oauth2-resource-server/6.2.4, Apache-2.0, approved, #11920 maven/mavencentral/org.springframework.security/spring-security-test/6.2.4, Apache-2.0, approved, #12922 maven/mavencentral/org.springframework.security/spring-security-web/6.2.4, Apache-2.0, approved, #11911 -maven/mavencentral/org.springframework/spring-aop/6.1.6, Apache-2.0, approved, #11755 -maven/mavencentral/org.springframework/spring-aspects/6.1.6, Apache-2.0, approved, #11905 -maven/mavencentral/org.springframework/spring-beans/6.1.6, Apache-2.0, approved, #11754 -maven/mavencentral/org.springframework/spring-context/6.1.6, Apache-2.0, approved, #11753 -maven/mavencentral/org.springframework/spring-core/6.1.6, Apache-2.0 AND BSD-3-Clause, approved, #11750 -maven/mavencentral/org.springframework/spring-expression/6.1.6, Apache-2.0, approved, #11747 -maven/mavencentral/org.springframework/spring-jcl/6.1.6, Apache-2.0, approved, #11749 -maven/mavencentral/org.springframework/spring-jdbc/6.1.6, Apache-2.0, approved, #11897 -maven/mavencentral/org.springframework/spring-orm/6.1.6, Apache-2.0, approved, #11924 -maven/mavencentral/org.springframework/spring-test/6.1.6, Apache-2.0, approved, #12919 -maven/mavencentral/org.springframework/spring-tx/6.1.6, Apache-2.0, approved, #11901 -maven/mavencentral/org.springframework/spring-web/6.1.6, Apache-2.0, approved, #11748 +maven/mavencentral/org.springframework/spring-aop/6.1.6, Apache-2.0, approved, #15221 +maven/mavencentral/org.springframework/spring-aspects/6.1.6, Apache-2.0, approved, #15193 +maven/mavencentral/org.springframework/spring-beans/6.1.6, Apache-2.0, approved, #15213 +maven/mavencentral/org.springframework/spring-context/6.1.6, Apache-2.0, approved, #15261 +maven/mavencentral/org.springframework/spring-core/6.1.6, Apache-2.0 AND BSD-3-Clause, approved, #15206 +maven/mavencentral/org.springframework/spring-expression/6.1.6, Apache-2.0, approved, #15264 +maven/mavencentral/org.springframework/spring-jcl/6.1.6, Apache-2.0, approved, #15266 +maven/mavencentral/org.springframework/spring-jdbc/6.1.6, Apache-2.0, approved, #15191 +maven/mavencentral/org.springframework/spring-orm/6.1.6, Apache-2.0, approved, #15278 +maven/mavencentral/org.springframework/spring-test/6.1.6, Apache-2.0, approved, #15265 +maven/mavencentral/org.springframework/spring-tx/6.1.6, Apache-2.0, approved, #15229 +maven/mavencentral/org.springframework/spring-web/6.1.6, Apache-2.0, approved, #15188 maven/mavencentral/org.springframework/spring-webflux/6.1.6, Apache-2.0, approved, #12593 -maven/mavencentral/org.springframework/spring-webmvc/6.1.6, Apache-2.0, approved, #11879 +maven/mavencentral/org.springframework/spring-webmvc/6.1.6, Apache-2.0, approved, #15182 maven/mavencentral/org.testcontainers/database-commons/1.19.8, Apache-2.0, approved, #10345 maven/mavencentral/org.testcontainers/jdbc/1.19.8, Apache-2.0, approved, #10348 maven/mavencentral/org.testcontainers/junit-jupiter/1.19.8, MIT, approved, #10344 diff --git a/bpdm-common-test/src/main/resources/keycloak/CX-Central.json b/bpdm-common-test/src/main/resources/keycloak/CX-Central.json index 45a9e4029..745d0bb88 100644 --- a/bpdm-common-test/src/main/resources/keycloak/CX-Central.json +++ b/bpdm-common-test/src/main/resources/keycloak/CX-Central.json @@ -378,6 +378,14 @@ "clientRole" : true, "containerId" : "0562ecfa-f17b-4d32-86cc-061f7da34b6b", "attributes" : { } + }, { + "id" : "e3ca0b50-95c7-43d5-baf6-359d87fc272a", + "name" : "upload_input_partner", + "description" : "Upload access to business partner input data", + "composite" : false, + "clientRole" : true, + "containerId" : "0562ecfa-f17b-4d32-86cc-061f7da34b6b", + "attributes" : {} } ], "account-console" : [ ], "EDC-GATE-OUTPUT-CONSUMER" : [ ], @@ -551,7 +559,7 @@ "attributes" : { }, "realmRoles" : [ ], "clientRoles" : { - "BPDM-GATE" : [ "write_sharing_state", "read_output_partner", "read_input_changelog", "read_stats", "read_output_changelog", "write_input_partner", "read_sharing_state", "read_input_partner" ] + "BPDM-GATE" : [ "write_sharing_state", "read_output_partner", "read_input_changelog", "read_stats", "read_output_changelog", "write_input_partner", "read_sharing_state", "read_input_partner", "upload_input_partner" ] }, "subGroups" : [ ] }, { @@ -571,7 +579,7 @@ "attributes" : { }, "realmRoles" : [ ], "clientRoles" : { - "BPDM-GATE" : [ "write_sharing_state", "read_input_changelog", "read_stats", "write_input_partner", "read_sharing_state", "read_input_partner" ] + "BPDM-GATE" : [ "write_sharing_state", "read_input_changelog", "read_stats", "write_input_partner", "read_sharing_state", "read_input_partner", "upload_input_partner" ] }, "subGroups" : [ ] }, { diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt new file mode 100644 index 000000000..c79b57aa9 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/GatePartnerUploadApi.kt @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import org.eclipse.tractusx.bpdm.gate.api.GateBusinessPartnerApi.Companion.BUSINESS_PARTNER_PATH +import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto +import org.eclipse.tractusx.bpdm.gate.api.model.response.PartnerUploadErrorResponse +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.multipart.MultipartFile + +@RequestMapping(BUSINESS_PARTNER_PATH, produces = [MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE]) +interface GatePartnerUploadApi { + + companion object{ + const val BUSINESS_PARTNER_PATH = ApiCommons.BASE_PATH + } + + @Operation( + summary = "Create or update business partners from uploaded CSV file", + description = "Create or update generic business partners. " + + "Updates instead of creating a new business partner if an already existing external ID is used. " + + "The same external ID may not occur more than once in a single request. " + + "For file upload request, the maximum number of business partners in file limited to \${bpdm.api.upsert-limit} entries.", + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "Business partners were successfully updated or created"), + ApiResponse(responseCode = "400", description = "On malformed Business partner upload request", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = PartnerUploadErrorResponse::class) + )]), + ] + ) + @PostMapping("/input/partner-upload-process", consumes = ["multipart/form-data"]) + fun uploadPartnerCsvFile( + @RequestPart("file") file: MultipartFile + ): ResponseEntity> + +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt index e1367bc3f..90aee224a 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClient.kt @@ -30,4 +30,6 @@ interface GateClient { val sharingState: SharingStateApiClient val stats: StatsApiClient + + val partnerUpload: PartnerUploadApiClient } diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt index 76c144482..2ebf4aad4 100644 --- a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/GateClientImpl.kt @@ -49,6 +49,8 @@ class GateClientImpl( override val stats by lazy { createClient() } + override val partnerUpload by lazy { createClient() } + private inline fun createClient() = httpServiceProxyFactory.createClient(T::class.java) } diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/PartnerUploadApiClient.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/PartnerUploadApiClient.kt new file mode 100644 index 000000000..4a86bca19 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/client/PartnerUploadApiClient.kt @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.client + +import org.eclipse.tractusx.bpdm.gate.api.GatePartnerUploadApi +import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.multipart.MultipartFile +import org.springframework.web.service.annotation.HttpExchange +import org.springframework.web.service.annotation.PostExchange + +@HttpExchange(GatePartnerUploadApi.BUSINESS_PARTNER_PATH) +interface PartnerUploadApiClient : GatePartnerUploadApi { + + @PostExchange( + url = "/input/partner-upload-process", + contentType = MediaType.MULTIPART_FORM_DATA_VALUE, + accept = ["application/json"] + ) + override fun uploadPartnerCsvFile( + @RequestPart("file") file: MultipartFile + ): ResponseEntity> + +} \ No newline at end of file diff --git a/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/PartnerUploadErrorResponse.kt b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/PartnerUploadErrorResponse.kt new file mode 100644 index 000000000..2e0dbec79 --- /dev/null +++ b/bpdm-gate-api/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/api/model/response/PartnerUploadErrorResponse.kt @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.api.model.response + +import io.swagger.v3.oas.annotations.media.Schema +import org.springframework.http.HttpStatus +import java.time.Instant + +@Schema(description = "Error response for invalid partner upload") +class PartnerUploadErrorResponse( + @Schema(description = "Timestamp of the error occurrence") + val timestamp: Instant, + @Schema(description = "HTTP status of the error response") + val status: HttpStatus, + @Schema(description = "List of error messages") + val error: List, + @Schema(description = "Request path where the error occurred") + val path: String +) \ No newline at end of file diff --git a/bpdm-gate/pom.xml b/bpdm-gate/pom.xml index ce9e675ac..234360bd8 100644 --- a/bpdm-gate/pom.xml +++ b/bpdm-gate/pom.xml @@ -161,6 +161,10 @@ org.eclipse.tractusx bpdm-orchestrator-api + + com.opencsv + opencsv + diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt index 55bd0f784..db2bd0dfc 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/config/PermissionConfigProperties.kt @@ -32,7 +32,8 @@ data class PermissionConfigProperties( val readOutputChangelog: String = "read_output_changelog", val readSharingState: String = "read_sharing_state", val writeSharingState: String = "write_sharing_state", - val readStats: String = "read_stats" + val readStats: String = "read_stats", + val uploadInputPartner: String = "upload_input_partner" ) { companion object { const val PREFIX = "bpdm.security.permissions" @@ -49,5 +50,6 @@ data class PermissionConfigProperties( const val READ_SHARING_STATE = "@$BEAN_QUALIFIER.getReadSharingState()" const val WRITE_SHARING_STATE = "@$BEAN_QUALIFIER.getWriteSharingState()" const val READ_STATS = "@$BEAN_QUALIFIER.getReadStats()" + const val UPLOAD_INPUT_PARTNER = "@$BEAN_QUALIFIER.getUploadInputPartner()" } } diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadController.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadController.kt new file mode 100644 index 000000000..576b0b06f --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadController.kt @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.controller + +import org.eclipse.tractusx.bpdm.gate.api.GatePartnerUploadApi +import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto +import org.eclipse.tractusx.bpdm.gate.config.ApiConfigProperties +import org.eclipse.tractusx.bpdm.gate.config.PermissionConfigProperties +import org.eclipse.tractusx.bpdm.gate.service.BusinessPartnerService +import org.eclipse.tractusx.bpdm.gate.service.PartnerUploadService +import org.eclipse.tractusx.bpdm.gate.util.getCurrentUserBpn +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile + +@RestController +class PartnerUploadController( + val businessPartnerService: BusinessPartnerService, + val apiConfigProperties: ApiConfigProperties, + val partnerUploadService: PartnerUploadService +) : GatePartnerUploadApi { + + @PreAuthorize("hasAuthority(${PermissionConfigProperties.UPLOAD_INPUT_PARTNER})") + override fun uploadPartnerCsvFile( + file: MultipartFile + ): ResponseEntity> { + return when { + file.isEmpty -> ResponseEntity(HttpStatus.BAD_REQUEST) + !file.contentType.equals("text/csv", ignoreCase = true) -> ResponseEntity(HttpStatus.BAD_REQUEST) + else -> partnerUploadService.processFile(file, getCurrentUserBpn()) + } + } + +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidPartnerUploadException.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidPartnerUploadException.kt new file mode 100644 index 000000000..a19bfce4e --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/BpdmInvalidPartnerUploadException.kt @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.exception + +class BpdmInvalidPartnerUploadException( + val errors: List +) : RuntimeException() \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt index bc0ba8053..36c3dadfc 100644 --- a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/exception/GateExceptionHandler.kt @@ -20,7 +20,25 @@ package org.eclipse.tractusx.bpdm.gate.exception import org.eclipse.tractusx.bpdm.common.exception.BpdmExceptionHandler +import org.eclipse.tractusx.bpdm.gate.api.model.response.PartnerUploadErrorResponse +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.context.request.WebRequest +import java.time.Instant @ControllerAdvice -class GateExceptionHandler : BpdmExceptionHandler() \ No newline at end of file +class GateExceptionHandler : BpdmExceptionHandler() { + + @ExceptionHandler(BpdmInvalidPartnerUploadException::class) + fun handleInvalidPartnerUploadException(ex:BpdmInvalidPartnerUploadException, request: WebRequest): ResponseEntity { + val errorResponse = PartnerUploadErrorResponse( + timestamp = Instant.now(), + status = HttpStatus.BAD_REQUEST, + error = ex.errors, + path = request.getDescription(false) + ) + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse) + } +} diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileHeader.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileHeader.kt new file mode 100644 index 000000000..a34377707 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileHeader.kt @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.model + +object PartnerUploadFileHeader { + const val EXTERNAL_ID = "externalId" + const val NAME_PARTS_1 = "nameParts1" + const val NAME_PARTS_2 = "nameParts2" + const val NAME_PARTS_3 = "nameParts3" + const val NAME_PARTS_4 = "nameParts4" + const val IDENTIFIERS_TYPE_1 = "identifiers1.type" + const val IDENTIFIERS_VALUE_1 = "identifiers1.value" + const val IDENTIFIERS_ISSUING_BODY_1 = "identifiers1.issuingBody" + const val IDENTIFIERS_TYPE_2 = "identifiers2.type" + const val IDENTIFIERS_VALUE_2 = "identifiers2.value" + const val IDENTIFIERS_ISSUING_BODY_2 = "identifiers2.issuingBody" + const val IDENTIFIERS_TYPE_3 = "identifiers3.type" + const val IDENTIFIERS_VALUE_3 = "identifiers3.value" + const val IDENTIFIERS_ISSUING_BODY_3 = "identifiers3.issuingBody" + const val STATES_VALID_FROM_1 = "states1.validFrom" + const val STATES_VALID_TO_1 = "states1.validTo" + const val STATES_TYPE_1 = "states1.type" + const val STATES_VALID_FROM_2 = "states2.validFrom" + const val STATES_VALID_TO_2 = "states2.validTo" + const val STATES_TYPE_2 = "states2.type" + const val ROLES = "roles" + const val IS_OWN_COMPANY_DATA = "isOwnCompanyData" + const val LEGAL_ENTITY_BPN = "legalEntity.legalEntityBpn" + const val LEGAL_ENTITY_NAME = "legalEntity.legalName" + const val LEGAL_ENTITY_SHORT_NAME = "legalEntity.shortName" + const val LEGAL_ENTITY_LEGAL_FORM = "legalEntity.legalForm" + const val LEGAL_ENTITY_STATES_VALID_FROM = "legalEntity.states.validFrom" + const val LEGAL_ENTITY_STATES_VALID_TO = "legalEntity.states.validTo" + const val LEGAL_ENTITY_STATES_TYPE = "legalEntity.states.type" + const val SITE_BPN = "site.siteBpn" + const val SITE_NAME = "site.name" + const val SITE_STATES_VALID_FROM = "site.states.validFrom" + const val SITE_STATES_VALID_TO = "site.states.validTo" + const val SITE_STATES_TYPE = "site.states.type" + const val ADDRESS_BPN = "address.addressBpn" + const val ADDRESS_NAME = "address.name" + const val ADDRESS_TYPE = "address.addressType" + const val PHYSICAL_POSTAL_ADDRESS_LONGITUDE = "address.physicalPostalAddress.geographicCoordinates.longitude" + const val PHYSICAL_POSTAL_ADDRESS_LATITUDE = "address.physicalPostalAddress.geographicCoordinates.latitude" + const val PHYSICAL_POSTAL_ADDRESS_ALTITUDE = "address.physicalPostalAddress.geographicCoordinates.altitude" + const val PHYSICAL_POSTAL_ADDRESS_COUNTRY = "address.physicalPostalAddress.country" + const val PHYSICAL_POSTAL_ADDRESS_ADMIN_AREA_1 = "address.physicalPostalAddress.administrativeAreaLevel1" + const val PHYSICAL_POSTAL_ADDRESS_ADMIN_AREA_2 = "address.physicalPostalAddress.administrativeAreaLevel2" + const val PHYSICAL_POSTAL_ADDRESS_ADMIN_AREA_3 = "address.physicalPostalAddress.administrativeAreaLevel3" + const val PHYSICAL_POSTAL_ADDRESS_POSTAL_CODE = "address.physicalPostalAddress.postalCode" + const val PHYSICAL_POSTAL_ADDRESS_CITY = "address.physicalPostalAddress.city" + const val PHYSICAL_POSTAL_ADDRESS_DISTRICT = "address.physicalPostalAddress.district" + const val PHYSICAL_POSTAL_ADDRESS_STREET_NAME_PREFIX = "address.physicalPostalAddress.street.namePrefix" + const val PHYSICAL_POSTAL_ADDRESS_STREET_ADDITIONAL_NAME_PREFIX = "address.physicalPostalAddress.street.additionalNamePrefix" + const val PHYSICAL_POSTAL_ADDRESS_STREET_NAME = "address.physicalPostalAddress.street.name" + const val PHYSICAL_POSTAL_ADDRESS_STREET_NAME_SUFFIX = "address.physicalPostalAddress.street.nameSuffix" + const val PHYSICAL_POSTAL_ADDRESS_STREET_ADDITIONAL_NAME_SUFFIX = "address.physicalPostalAddress.street.additionalNameSuffix" + const val PHYSICAL_POSTAL_ADDRESS_STREET_HOUSE_NUMBER = "address.physicalPostalAddress.street.houseNumber" + const val PHYSICAL_POSTAL_ADDRESS_STREET_HOUSE_NUMBER_SUPPLEMENT = "address.physicalPostalAddress.street.houseNumberSupplement" + const val PHYSICAL_POSTAL_ADDRESS_STREET_MILESTONE = "address.physicalPostalAddress.street.milestone" + const val PHYSICAL_POSTAL_ADDRESS_STREET_DIRECTION = "address.physicalPostalAddress.street.direction" + const val PHYSICAL_POSTAL_ADDRESS_COMPANY_POSTAL_CODE = "address.physicalPostalAddress.companyPostalCode" + const val PHYSICAL_POSTAL_ADDRESS_INDUSTRIAL_ZONE = "address.physicalPostalAddress.industrialZone" + const val PHYSICAL_POSTAL_ADDRESS_BUILDING = "address.physicalPostalAddress.building" + const val PHYSICAL_POSTAL_ADDRESS_FLOOR = "address.physicalPostalAddress.floor" + const val PHYSICAL_POSTAL_ADDRESS_DOOR = "address.physicalPostalAddress.door" + const val ALTERNATIVE_POSTAL_ADDRESS_LONGITUDE = "address.alternativePostalAddress.geographicCoordinates.longitude" + const val ALTERNATIVE_POSTAL_ADDRESS_LATITUDE = "address.alternativePostalAddress.geographicCoordinates.latitude" + const val ALTERNATIVE_POSTAL_ADDRESS_ALTITUDE = "address.alternativePostalAddress.geographicCoordinates.altitude" + const val ALTERNATIVE_POSTAL_ADDRESS_COUNTRY = "address.alternativePostalAddress.country" + const val ALTERNATIVE_POSTAL_ADDRESS_ADMIN_AREA_1 = "address.alternativePostalAddress.administrativeAreaLevel1" + const val ALTERNATIVE_POSTAL_ADDRESS_POSTAL_CODE = "address.alternativePostalAddress.postalCode" + const val ALTERNATIVE_POSTAL_ADDRESS_CITY = "address.alternativePostalAddress.city" + const val ALTERNATIVE_POSTAL_ADDRESS_DELIVERY_SERVICE_TYPE = "address.alternativePostalAddress.deliveryServiceType" + const val ALTERNATIVE_POSTAL_ADDRESS_DELIVERY_SERVICE_QUALIFIER = "address.alternativePostalAddress.deliveryServiceQualifier" + const val ALTERNATIVE_POSTAL_ADDRESS_DELIVERY_SERVICE_NUMBER = "address.alternativePostalAddress.deliveryServiceNumber" + const val ADDRESS_STATES_VALID_FROM = "address.states.validForm" + const val ADDRESS_STATES_VALID_TO = "address.states.validTo" + const val ADDRESS_STATES_TYPE = "address.states.type" +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileRow.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileRow.kt new file mode 100644 index 000000000..d36181e5d --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/model/PartnerUploadFileRow.kt @@ -0,0 +1,249 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.model + +import com.opencsv.bean.CsvBindByName +import jakarta.validation.constraints.NotEmpty + +data class PartnerUploadFileRow( + + @CsvBindByName(column = PartnerUploadFileHeader.EXTERNAL_ID) + @field:NotEmpty(message = "Column 'externalId' is missing and can not be empty") + val externalId: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.NAME_PARTS_1) + val nameParts1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.NAME_PARTS_2) + val nameParts2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.NAME_PARTS_3) + val nameParts3: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.NAME_PARTS_4) + val nameParts4: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_TYPE_1) + val identifiersType1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_VALUE_1) + val identifiersValue1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_ISSUING_BODY_1) + val identifiersIssuingBody1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_TYPE_2) + val identifiersType2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_VALUE_2) + val identifiersValue2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_ISSUING_BODY_2) + val identifiersIssuingBody2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_TYPE_3) + val identifiersType3: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_VALUE_3) + val identifiersValue3: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IDENTIFIERS_ISSUING_BODY_3) + val identifiersIssuingBody3: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.STATES_VALID_FROM_1) + val statesValidFrom1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.STATES_VALID_TO_1) + val statesValidTo1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.STATES_TYPE_1) + val statesType1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.STATES_VALID_FROM_2) + val statesValidFrom2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.STATES_VALID_TO_2) + val statesValidTo2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.STATES_TYPE_2) + val statesType2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ROLES) + val roles: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.IS_OWN_COMPANY_DATA) + val isOwnCompanyData: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_BPN) + val legalEntityBpn: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_NAME) + val legalEntityName: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_SHORT_NAME) + val legalEntityShortName: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_LEGAL_FORM) + val legalEntityLegalForm: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_FROM) + val legalEntityStatesValidFrom: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_TO) + val legalEntityStatesValidTo: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.LEGAL_ENTITY_STATES_TYPE) + val legalEntityStatesType: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.SITE_BPN) + val siteBpn: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.SITE_NAME) + val siteName: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.SITE_STATES_VALID_FROM) + val siteStatesValidFrom: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.SITE_STATES_VALID_TO) + val siteStatesValidTo: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.SITE_STATES_TYPE) + val siteStatesType: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ADDRESS_BPN) + val addressBpn: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ADDRESS_NAME) + val addressName: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ADDRESS_TYPE) + val addressType: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_LONGITUDE) + val physicalPostalAddressLongitude: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_LATITUDE) + val physicalPostalAddressLatitude: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_ALTITUDE) + val physicalPostalAddressAltitude: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_COUNTRY) + val physicalPostalAddressCountry: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_ADMIN_AREA_1) + val physicalPostalAddressAdminArea1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_ADMIN_AREA_2) + val physicalPostalAddressAdminArea2: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_ADMIN_AREA_3) + val physicalPostalAddressAdminArea3: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_POSTAL_CODE) + val physicalPostalAddressPostalCode: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_CITY) + val physicalPostalAddressCity: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_DISTRICT) + val physicalPostalAddressDistrict: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_NAME_PREFIX) + val physicalPostalAddressStreetNamePrefix: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_ADDITIONAL_NAME_PREFIX) + val physicalPostalAddressStreetAdditionalNamePrefix: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_NAME) + val physicalPostalAddressStreetName: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_NAME_SUFFIX) + val physicalPostalAddressStreetNameSuffix: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_ADDITIONAL_NAME_SUFFIX) + val physicalPostalAddressStreetAdditionalNameSuffix: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_HOUSE_NUMBER) + val physicalPostalAddressStreetHouseNumber: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_HOUSE_NUMBER_SUPPLEMENT) + val physicalPostalAddressStreetHouseNumberSupplement: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_MILESTONE) + val physicalPostalAddressStreetMilestone: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_STREET_DIRECTION) + val physicalPostalAddressStreetDirection: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_COMPANY_POSTAL_CODE) + val physicalPostalAddressCompanyPostalCode: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_INDUSTRIAL_ZONE) + val physicalPostalAddressIndustrialZone: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_BUILDING) + val physicalPostalAddressBuilding: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_FLOOR) + val physicalPostalAddressFloor: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_DOOR) + val physicalPostalAddressDoor: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_LONGITUDE) + val alternativePostalAddressLongitude: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_LATITUDE) + val alternativePostalAddressLatitude: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_ALTITUDE) + val alternativePostalAddressAltitude: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_COUNTRY) + val alternativePostalAddressCountry: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_ADMIN_AREA_1) + val alternativePostalAddressAdminArea1: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_POSTAL_CODE) + val alternativePostalAddressPostalCode: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_CITY) + val alternativePostalAddressCity: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_DELIVERY_SERVICE_TYPE) + val alternativePostalAddressDeliveryServiceType: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_DELIVERY_SERVICE_QUALIFIER) + val alternativePostalAddressDeliveryServiceQualifier: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_DELIVERY_SERVICE_NUMBER) + val alternativePostalAddressDeliveryServiceNumber: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ADDRESS_STATES_VALID_FROM) + val addressStatesValidFrom: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ADDRESS_STATES_VALID_TO) + val addressStatesValidTo: String? = null, + + @CsvBindByName(column = PartnerUploadFileHeader.ADDRESS_STATES_TYPE) + val addressStatesType: String? = null +) diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt new file mode 100644 index 000000000..c00ffe0be --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/service/PartnerUploadService.kt @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.service + +import mu.KotlinLogging +import org.eclipse.tractusx.bpdm.gate.api.model.response.BusinessPartnerInputDto +import org.eclipse.tractusx.bpdm.gate.model.PartnerUploadFileRow +import org.eclipse.tractusx.bpdm.gate.util.PartnerFileUtil +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile + +@Service +class PartnerUploadService( + private val businessPartnerService: BusinessPartnerService +) { + + private val logger = KotlinLogging.logger { } + + fun processFile(file: MultipartFile, ownerBpnl: String?): ResponseEntity> { + val csvData: List = PartnerFileUtil.parseCsv(file) + val businessPartnerDtos = PartnerFileUtil.validateAndMapToBusinessPartnerInputRequests(csvData) + val result = businessPartnerService.upsertBusinessPartnersInput(businessPartnerDtos, ownerBpnl) + return ResponseEntity.ok(result) + } + +} \ No newline at end of file diff --git a/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt new file mode 100644 index 000000000..95a2794c3 --- /dev/null +++ b/bpdm-gate/src/main/kotlin/org/eclipse/tractusx/bpdm/gate/util/PartnerFileUtil.kt @@ -0,0 +1,322 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.util + +import com.neovisionaries.i18n.CountryCode +import com.opencsv.bean.CsvToBeanBuilder +import jakarta.validation.Validation +import jakarta.validation.Validator +import mu.KotlinLogging +import org.eclipse.tractusx.bpdm.common.dto.AddressType +import org.eclipse.tractusx.bpdm.common.dto.BusinessPartnerRole +import org.eclipse.tractusx.bpdm.common.dto.GeoCoordinateDto +import org.eclipse.tractusx.bpdm.common.model.BusinessStateType +import org.eclipse.tractusx.bpdm.common.model.DeliveryServiceType +import org.eclipse.tractusx.bpdm.gate.api.model.* +import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequest +import org.eclipse.tractusx.bpdm.gate.api.model.response.AddressRepresentationInputDto +import org.eclipse.tractusx.bpdm.gate.api.model.response.LegalEntityRepresentationInputDto +import org.eclipse.tractusx.bpdm.gate.api.model.response.SiteRepresentationInputDto +import org.eclipse.tractusx.bpdm.gate.exception.BpdmInvalidPartnerUploadException +import org.eclipse.tractusx.bpdm.gate.model.PartnerUploadFileHeader +import org.eclipse.tractusx.bpdm.gate.model.PartnerUploadFileRow +import org.springframework.web.multipart.MultipartFile +import java.io.InputStreamReader +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException + +object PartnerFileUtil { + + private val logger = KotlinLogging.logger { } + + fun parseCsv(file: MultipartFile): List { + val reader = InputStreamReader(file.inputStream) + return CsvToBeanBuilder(reader) + .withType(PartnerUploadFileRow::class.java) + .withIgnoreLeadingWhiteSpace(true) + .build() + .parse() + } + + /** + * Validates and maps a list of CSV data rows to a list of BusinessPartnerInputRequest objects. + * + * This function performs the following operations: + * 1. Validates each row of CSV data using a provided validator. + * 2. Converts each valid row into a BusinessPartnerInputRequest object. + * 3. Collects any validation errors and throws an exception if any errors are found. + * + * @param csvData A list of PartnerUploadFileRow objects representing the rows of the CSV file to be processed. + * @return A list of BusinessPartnerInputRequest objects derived from the valid CSV rows. + * @throws BpdmInvalidPartnerUploadException if any validation errors are encountered during processing. + */ + fun validateAndMapToBusinessPartnerInputRequests(csvData: List): List { + val formatter = DateTimeFormatter.ISO_DATE_TIME + val validator: Validator = Validation.buildDefaultValidatorFactory().validator + val errors = mutableListOf() + val externalIdSet = mutableSetOf() + + val requests = csvData.mapIndexedNotNull { index, row -> + try { + validateRow(row, index, validator, errors, externalIdSet) + BusinessPartnerInputRequest( + externalId = row.externalId.orEmpty(), + nameParts = listOfNotNull(row.nameParts1, row.nameParts2, row.nameParts3, row.nameParts4).filter { it.isNotEmpty() }, + identifiers = listOfNotNull( + row.toIdentifierDto(1), row.toIdentifierDto(2), row.toIdentifierDto(3) + ), + states = listOfNotNull( + row.toStateDto(1, formatter, errors, index), + row.toStateDto(2, formatter, errors, index) + ), + roles = row.roles.toEnumList(BusinessPartnerRole::valueOf, errors, index + 1, PartnerUploadFileHeader.ROLES), + isOwnCompanyData = row.isOwnCompanyData?.toBoolean() ?: false, + legalEntity = row.toLegalEntityRepresentationInputDto(formatter, errors, index), + site = row.toSiteRepresentationInputDto(formatter, errors, index), + address = row.toAddressRepresentationInputDto(formatter, errors, index) + ) + } catch (e: Exception) { + errors.add("Row ${index + 1} has error: ${e.message}") + null + } + } + + if (errors.isNotEmpty()) { + throw BpdmInvalidPartnerUploadException(errors) + } + + return requests + } + + private fun validateRow( + row: PartnerUploadFileRow, + index: Int, + validator: Validator, + errors: MutableList, + externalIdSet: MutableSet + ) { + val violations = validator.validate(row) + logger.debug { "Validating row ${index + 1}: $row" } + if (violations.isNotEmpty()) { + val violationMessages = violations.joinToString("; ") { it.message } + errors.add("Row ${index + 1} has error: $violationMessages") + } + + if (row.externalId.isNullOrBlank()) { + errors.add("Row ${index + 1} has error: Column 'externalId' is null or blank.") + } else if (!externalIdSet.add(row.externalId)) { + errors.add("Row ${index + 1} has error: Column 'externalId' already exists.") + } + } + + private fun PartnerUploadFileRow.toIdentifierDto(index: Int): BusinessPartnerIdentifierDto? { + val type = when (index) { + 1 -> identifiersType1 + 2 -> identifiersType2 + 3 -> identifiersType3 + else -> null + } + val value = when (index) { + 1 -> identifiersValue1 + 2 -> identifiersValue2 + 3 -> identifiersValue3 + else -> null + } + val issuingBody = when (index) { + 1 -> identifiersIssuingBody1 + 2 -> identifiersIssuingBody2 + 3 -> identifiersIssuingBody3 + else -> null + }?.takeIf { it.isNotEmpty() } // Convert empty string to null + + return if (type.isNullOrEmpty() || value.isNullOrEmpty()) null + else BusinessPartnerIdentifierDto(type, value, issuingBody) + } + + private fun PartnerUploadFileRow.toStateDto( + index: Int, + formatter: DateTimeFormatter, + errors: MutableList, + rowIndex: Int + ): BusinessPartnerStateDto? { + val validFrom = when (index) { + 1 -> statesValidFrom1 + 2 -> statesValidFrom2 + else -> null + } + val validTo = when (index) { + 1 -> statesValidTo1 + 2 -> statesValidTo2 + else -> null + } + val type = when (index) { + 1 -> statesType1 + 2 -> statesType2 + else -> null + } + if (validFrom.isNullOrEmpty() && validTo.isNullOrEmpty() && type.isNullOrEmpty()) return null + return BusinessPartnerStateDto( + validFrom = validFrom?.let { parseDate(it, formatter, errors, rowIndex + 1, "states$index.validFrom") }, + validTo = validTo?.let { parseDate(it, formatter, errors, rowIndex + 1, "states$index.validTo") }, + type = type?.let { parseEnum(it, BusinessStateType::valueOf, errors, rowIndex + 1, "states$index.type") } + ) + } + + private fun PartnerUploadFileRow.toLegalEntityRepresentationInputDto( + formatter: DateTimeFormatter, + errors: MutableList, + rowIndex: Int + ): LegalEntityRepresentationInputDto { + return LegalEntityRepresentationInputDto( + legalEntityBpn = legalEntityBpn?.takeIf { it.isNotEmpty() }, + legalName = legalEntityName?.takeIf { it.isNotEmpty() }, + shortName = legalEntityShortName?.takeIf { it.isNotEmpty() }, + legalForm = legalEntityLegalForm?.takeIf { it.isNotEmpty() }, + states = listOfNotNull( + if (!legalEntityStatesValidFrom.isNullOrEmpty() && !legalEntityStatesValidTo.isNullOrEmpty() && !legalEntityStatesType.isNullOrEmpty()) + BusinessPartnerStateDto( + validFrom = parseDate(legalEntityStatesValidFrom, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_FROM), + validTo = parseDate(legalEntityStatesValidTo, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.LEGAL_ENTITY_STATES_VALID_TO), + type = parseEnum(legalEntityStatesType, BusinessStateType::valueOf, errors, rowIndex + 1, PartnerUploadFileHeader.LEGAL_ENTITY_STATES_TYPE) + ) else null + ) + ) + } + + private fun PartnerUploadFileRow.toSiteRepresentationInputDto( + formatter: DateTimeFormatter, + errors: MutableList, + rowIndex: Int + ): SiteRepresentationInputDto { + return SiteRepresentationInputDto( + siteBpn = siteBpn?.takeIf { it.isNotEmpty() }, + name = siteName?.takeIf { it.isNotEmpty() }, + states = listOfNotNull( + if (!siteStatesValidFrom.isNullOrEmpty() && !siteStatesValidTo.isNullOrEmpty() && !siteStatesType.isNullOrEmpty()) + BusinessPartnerStateDto( + validFrom = parseDate(siteStatesValidFrom, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.SITE_STATES_VALID_FROM), + validTo = parseDate(siteStatesValidTo, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.SITE_STATES_VALID_TO), + type = parseEnum(siteStatesType, BusinessStateType::valueOf, errors, rowIndex + 1, PartnerUploadFileHeader.SITE_STATES_TYPE) + ) else null + ) + ) + } + + private fun PartnerUploadFileRow.toAddressRepresentationInputDto( + formatter: DateTimeFormatter, + errors: MutableList, + rowIndex: Int + ): AddressRepresentationInputDto { + return AddressRepresentationInputDto( + addressBpn = addressBpn?.takeIf { it.isNotEmpty() }, + name = addressName?.takeIf { it.isNotEmpty() }, + addressType = addressType?.let { parseEnum(it, AddressType::valueOf, errors, rowIndex + 1, PartnerUploadFileHeader.ADDRESS_TYPE) }, + physicalPostalAddress = PhysicalPostalAddressDto( + geographicCoordinates = GeoCoordinateDto( + longitude = physicalPostalAddressLongitude?.toFloatOrNull() ?: 0f, + latitude = physicalPostalAddressLatitude?.toFloatOrNull() ?: 0f, + altitude = physicalPostalAddressAltitude?.toFloatOrNull() ?: 0f + ), + country = physicalPostalAddressCountry?.let { parseEnum(it, CountryCode::valueOf, errors, rowIndex + 1, PartnerUploadFileHeader.PHYSICAL_POSTAL_ADDRESS_COUNTRY) }, + administrativeAreaLevel1 = physicalPostalAddressAdminArea1?.takeIf { it.isNotEmpty() }, + administrativeAreaLevel2 = physicalPostalAddressAdminArea2?.takeIf { it.isNotEmpty() }, + administrativeAreaLevel3 = physicalPostalAddressAdminArea3?.takeIf { it.isNotEmpty() }, + postalCode = physicalPostalAddressPostalCode?.takeIf { it.isNotEmpty() }, + city = physicalPostalAddressCity?.takeIf { it.isNotEmpty() }, + district = physicalPostalAddressDistrict?.takeIf { it.isNotEmpty() }, + street = StreetDto( + namePrefix = physicalPostalAddressStreetAdditionalNamePrefix?.takeIf { it.isNotEmpty() }, + additionalNamePrefix = physicalPostalAddressStreetAdditionalNameSuffix?.takeIf { it.isNotEmpty() }, + name = physicalPostalAddressStreetName?.takeIf { it.isNotEmpty() }, + nameSuffix = physicalPostalAddressStreetNameSuffix?.takeIf { it.isNotEmpty() }, + additionalNameSuffix = physicalPostalAddressStreetAdditionalNameSuffix?.takeIf { it.isNotEmpty() }, + houseNumber = physicalPostalAddressStreetHouseNumber, + houseNumberSupplement = physicalPostalAddressStreetHouseNumberSupplement?.takeIf { it.isNotEmpty() }, + milestone = physicalPostalAddressStreetMilestone?.takeIf { it.isNotEmpty() }, + direction = physicalPostalAddressStreetDirection?.takeIf { it.isNotEmpty() } + ), + companyPostalCode = physicalPostalAddressCompanyPostalCode?.takeIf { it.isNotEmpty() }, + industrialZone = physicalPostalAddressIndustrialZone?.takeIf { it.isNotEmpty() }, + building = physicalPostalAddressBuilding?.takeIf { it.isNotEmpty() }, + floor = physicalPostalAddressFloor?.takeIf { it.isNotEmpty() }, + door = physicalPostalAddressDoor?.takeIf { it.isNotEmpty() } + ), + alternativePostalAddress = AlternativePostalAddressDto( + geographicCoordinates = GeoCoordinateDto( + longitude = alternativePostalAddressLongitude?.toFloatOrNull() ?: 0f, + latitude = alternativePostalAddressLatitude?.toFloatOrNull() ?: 0f, + altitude = alternativePostalAddressAltitude?.toFloatOrNull() ?: 0f + ), + country = alternativePostalAddressCountry?.let { parseEnum(it, CountryCode::valueOf, errors, rowIndex + 1, PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_COUNTRY) }, + administrativeAreaLevel1 = alternativePostalAddressAdminArea1?.takeIf { it.isNotEmpty() }, + postalCode = alternativePostalAddressPostalCode?.takeIf { it.isNotEmpty() }, + city = alternativePostalAddressCity?.takeIf { it.isNotEmpty() }, + deliveryServiceType = alternativePostalAddressDeliveryServiceType?.let { + parseEnum( + it, + DeliveryServiceType::valueOf, + errors, + rowIndex + 1, + PartnerUploadFileHeader.ALTERNATIVE_POSTAL_ADDRESS_DELIVERY_SERVICE_TYPE + ) + }, + deliveryServiceQualifier = alternativePostalAddressDeliveryServiceQualifier?.takeIf { it.isNotEmpty() }, + deliveryServiceNumber = alternativePostalAddressDeliveryServiceNumber?.takeIf { it.isNotEmpty() } + ), + states = listOfNotNull( + if (!addressStatesValidFrom.isNullOrEmpty() && !addressStatesValidTo.isNullOrEmpty() && !addressStatesType.isNullOrEmpty()) + BusinessPartnerStateDto( + validFrom = parseDate(addressStatesValidFrom, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.ADDRESS_STATES_VALID_FROM), + validTo = parseDate(addressStatesValidTo, formatter, errors, rowIndex + 1, PartnerUploadFileHeader.ADDRESS_STATES_VALID_TO), + type = parseEnum(addressStatesType, BusinessStateType::valueOf, errors, rowIndex + 1, PartnerUploadFileHeader.ADDRESS_STATES_TYPE) + ) else null + ) + ) + } + + private fun parseDate(dateString: String?, formatter: DateTimeFormatter, errors: MutableList, rowIndex: Int, fieldName: String): LocalDateTime? { + return try { + dateString?.let { LocalDateTime.parse(it, formatter) } + } catch (e: DateTimeParseException) { + errors.add("Row $rowIndex has error: Invalid date format in field '$fieldName'.") + null + } + } + + private fun > parseEnum(value: String?, enumValueOf: (String) -> T, errors: MutableList, rowIndex: Int, fieldName: String): T? { + return try { + value?.let { enumValueOf(it) } + } catch (e: IllegalArgumentException) { + errors.add("Row $rowIndex has error: Invalid enum value in field '$fieldName'.") + null + } + } + + private fun > String?.toEnumList(enumValueOf: (String) -> T, errors: MutableList, rowIndex: Int, fieldName: String): List { + return try { + this?.split(",")?.map { enumValueOf(it.trim()) } ?: emptyList() + } catch (e: IllegalArgumentException) { + errors.add("Row $rowIndex has error: Invalid enum value in field '$fieldName'.") + emptyList() + } + } + +} \ No newline at end of file diff --git a/bpdm-gate/src/main/resources/application.yml b/bpdm-gate/src/main/resources/application.yml index df8dc8b86..3f995ffd8 100644 --- a/bpdm-gate/src/main/resources/application.yml +++ b/bpdm-gate/src/main/resources/application.yml @@ -123,6 +123,8 @@ bpdm: writeSharingState: write_sharing_state # Name of the permission to read business partner statistics read_stats: read_stats + # Name of the permission to upload business partner entries for business partner input data + uploadInputPartner: upload_input_partner datasource: # Host name of the used datasource host: localhost @@ -193,6 +195,10 @@ spring: batch_size: 16 order_inserts: true order_updates: true + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB springdoc: api-docs: # Generate Open-API document @@ -211,3 +217,4 @@ springdoc: + diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt index 62f5a8c3a..719ff8a75 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthAdminIT.kt @@ -61,6 +61,9 @@ class AuthAdminIT @Autowired constructor( getStage = AuthExpectationType.Authorized, getAddressType = AuthExpectationType.Authorized, getConfidenceCriteria = AuthExpectationType.Authorized + ), + uploadPartner = UploadPartnerAuthExpections( + postInput = AuthExpectationType.Authorized ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt index 34cbd50e7..cad57b87f 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputConsumerIT.kt @@ -61,6 +61,9 @@ class AuthInputConsumerIT @Autowired constructor( getStage = AuthExpectationType.Authorized, getAddressType = AuthExpectationType.Authorized, getConfidenceCriteria = AuthExpectationType.Authorized + ), + uploadPartner = UploadPartnerAuthExpections( + postInput = AuthExpectationType.Forbidden ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt index f30215553..3d1bcaee8 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthInputManagerIT.kt @@ -61,6 +61,9 @@ class AuthInputManagerIT @Autowired constructor( getStage = AuthExpectationType.Authorized, getAddressType = AuthExpectationType.Authorized, getConfidenceCriteria = AuthExpectationType.Authorized + ), + uploadPartner = UploadPartnerAuthExpections( + postInput = AuthExpectationType.Authorized ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt index 1daa875f9..b98e54369 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthOutputConsumerIT.kt @@ -61,6 +61,9 @@ class AuthOutputConsumerIT @Autowired constructor( getStage = AuthExpectationType.Authorized, getAddressType = AuthExpectationType.Authorized, getConfidenceCriteria = AuthExpectationType.Authorized + ), + uploadPartner = UploadPartnerAuthExpections( + postInput = AuthExpectationType.Forbidden ) ) ) { diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt index 4d4240360..868a078df 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/AuthTestBase.kt @@ -28,6 +28,9 @@ import org.eclipse.tractusx.bpdm.gate.api.model.request.PostSharingStateReadyReq import org.eclipse.tractusx.bpdm.test.util.AuthAssertionHelper import org.eclipse.tractusx.bpdm.test.util.AuthExpectationType import org.junit.jupiter.api.Test +import org.springframework.mock.web.MockMultipartFile +import java.nio.file.Files +import java.nio.file.Paths abstract class AuthTestBase( private val gateClient: GateClient, @@ -91,13 +94,21 @@ abstract class AuthTestBase( authAssertions.assert(authExpectations.stats.getConfidenceCriteria) { gateClient.stats.getConfidenceCriteriaStats() } } + @Test + fun `POST Partner Input`() { + val bytes = Files.readAllBytes(Paths.get("src/test/resources/testData/valid_partner_data.csv")) + val uploadedFile = MockMultipartFile("valid_partner_data.csv", "valid_partner_data.csv", "text/csv", bytes) + authAssertions.assert(authExpectations.uploadPartner.postInput) { gateClient.partnerUpload.uploadPartnerCsvFile(uploadedFile) } + } + } data class GateAuthExpectations( val businessPartner: BusinessPartnerAuthExpectations, val changelog: ChangelogAuthExpectations, val sharingState: SharingStateAuthExpectations, - val stats: StatsAuthExpectations + val stats: StatsAuthExpectations, + val uploadPartner: UploadPartnerAuthExpections ) data class BusinessPartnerAuthExpectations( @@ -123,3 +134,6 @@ data class StatsAuthExpectations( val getConfidenceCriteria: AuthExpectationType ) +data class UploadPartnerAuthExpections( + val postInput: AuthExpectationType +) diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt index c1852a3af..db05c7b7c 100644 --- a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/auth/NoAuthIT.kt @@ -59,6 +59,9 @@ class NoAuthIT @Autowired constructor( getStage = AuthExpectationType.Unauthorized, getAddressType = AuthExpectationType.Unauthorized, getConfidenceCriteria = AuthExpectationType.Unauthorized + ), + uploadPartner = UploadPartnerAuthExpections( + postInput = AuthExpectationType.Unauthorized ) ) ) diff --git a/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt new file mode 100644 index 000000000..97b1af165 --- /dev/null +++ b/bpdm-gate/src/test/kotlin/org/eclipse/tractusx/bpdm/gate/controller/PartnerUploadControllerIT.kt @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.bpdm.gate.controller + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import com.github.tomakehurst.wiremock.junit5.WireMockExtension +import org.eclipse.tractusx.bpdm.gate.api.client.GateClient +import org.eclipse.tractusx.bpdm.gate.api.model.SharingStateType +import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequest +import org.eclipse.tractusx.bpdm.gate.api.model.response.SharingStateDto +import org.eclipse.tractusx.bpdm.gate.service.GoldenRecordTaskService +import org.eclipse.tractusx.bpdm.gate.util.MockAndAssertUtils +import org.eclipse.tractusx.bpdm.test.containers.PostgreSQLContextInitializer +import org.eclipse.tractusx.bpdm.test.testdata.gate.BusinessPartnerNonVerboseValues +import org.eclipse.tractusx.bpdm.test.testdata.gate.BusinessPartnerVerboseValues +import org.eclipse.tractusx.bpdm.test.util.AssertHelpers +import org.eclipse.tractusx.bpdm.test.util.DbTestHelpers +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.RegisterExtension +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpStatus +import org.springframework.mock.web.MockMultipartFile +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.springframework.web.reactive.function.client.WebClientResponseException +import java.nio.file.Files +import java.nio.file.Paths + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT +) +@ActiveProfiles("test-no-auth") +@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) +class PartnerUploadControllerIT @Autowired constructor( + val testHelpers: DbTestHelpers, + val assertHelpers: AssertHelpers, + val gateClient: GateClient, + val goldenRecordTaskService: GoldenRecordTaskService, + val mockAndAssertUtils: MockAndAssertUtils +){ + + companion object { + + @JvmField + @RegisterExtension + val orchestratorWireMockServer: WireMockExtension = WireMockExtension.newInstance().options(WireMockConfiguration.wireMockConfig().dynamicPort()).build() + + @JvmField + @RegisterExtension + val poolWireMockServer: WireMockExtension = WireMockExtension.newInstance().options(WireMockConfiguration.wireMockConfig().dynamicPort()).build() + + + @JvmStatic + @DynamicPropertySource + fun properties(registry: DynamicPropertyRegistry) { + registry.add("bpdm.client.pool.base-url") { poolWireMockServer.baseUrl() } + registry.add("bpdm.client.orchestrator.base-url") { orchestratorWireMockServer.baseUrl() } + } + } + + @BeforeEach + fun beforeEach() { + testHelpers.truncateDbTables() + orchestratorWireMockServer.resetAll() + poolWireMockServer.resetAll() + this.mockAndAssertUtils.mockOrchestratorApi(orchestratorWireMockServer) + } + + @Test + fun testPartnerDataFileUploadScenarios() { + // Test valid CSV file upload + testFileUpload("src/test/resources/testData/valid_partner_data.csv", HttpStatus.OK) + + // Test empty CSV file upload + testFileUpload("src/test/resources/testData/empty_partner_data.csv", HttpStatus.BAD_REQUEST) + + // Test non-CSV file upload + testFileUpload("src/test/resources/testData/non_csv_partner_data.xls", HttpStatus.BAD_REQUEST) + + // Test invalid CSV file upload - contains bad business partner data + testFileUpload("src/test/resources/testData/invalid_partner_data.csv", HttpStatus.BAD_REQUEST) + } + + @Test + fun testUploadPartnerDataAndCheckSharingState() { + val bytes = Files.readAllBytes(Paths.get("src/test/resources/testData/valid_partner_data.csv")) + val uploadedFile = MockMultipartFile("valid_partner_data.csv", "valid_partner_data.csv", "text/csv", bytes) + + uploadBusinessPartnerRecordAndShare(uploadedFile) + + val externalId1 = BusinessPartnerVerboseValues.externalId1 + val externalId2 = BusinessPartnerVerboseValues.externalId2 + + val externalIds = listOf(externalId1, externalId2) + + val sharingStatesRequests = listOf( + SharingStateDto( + externalId = externalId1, + sharingStateType = SharingStateType.Pending, + sharingErrorCode = null, + sharingErrorMessage = null, + sharingProcessStarted = null, + taskId = "0" + ), + SharingStateDto( + externalId = externalId2, + sharingStateType = SharingStateType.Pending, + sharingErrorCode = null, + sharingErrorMessage = null, + sharingProcessStarted = null, + taskId = "1" + ) + ) + + val sharingStateResponses = this.mockAndAssertUtils.readSharingStates(externalIds) + assertHelpers.assertRecursively(sharingStateResponses).isEqualTo(sharingStatesRequests) + } + + @Test + fun testUploadCsvAndValidateBusinessPartnerData() { + // Read the bytes of the CSV file + val bytes = Files.readAllBytes(Paths.get("src/test/resources/testData/valid_partner_data.csv")) + val uploadedFile = MockMultipartFile("valid_partner_data.csv", "valid_partner_data.csv", "text/csv", bytes) + + // Upload the CSV file + val uploadResponse = gateClient.partnerUpload.uploadPartnerCsvFile(uploadedFile).body!! + val expectedResponse = listOf( + BusinessPartnerVerboseValues.bpInputRequestFull, + BusinessPartnerNonVerboseValues.bpInputRequestFull.fastCopy(externalId = BusinessPartnerVerboseValues.externalId2, shortName = "2") + ) + + val searchResponsePage = gateClient.businessParters.getBusinessPartnersInput(listOf(BusinessPartnerVerboseValues.externalId1, BusinessPartnerVerboseValues.externalId2)).content + this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(uploadResponse, expectedResponse) + this.mockAndAssertUtils.assertUpsertResponsesMatchRequests(searchResponsePage, expectedResponse) + } + + + private fun testFileUpload(filePath: String, expectedStatus: HttpStatus) { + val bytes = Files.readAllBytes(Paths.get(filePath)) + val file = MockMultipartFile(filePath.substringAfterLast('/'), filePath.substringAfterLast('/'), determineFileType(filePath), bytes) + + if (expectedStatus == HttpStatus.OK) { + val response = gateClient.partnerUpload.uploadPartnerCsvFile(file) + assertEquals(expectedStatus, response.statusCode) + } else { + val exception = assertThrows { + gateClient.partnerUpload.uploadPartnerCsvFile(file) + } + assertEquals(expectedStatus, exception.statusCode) + } + } + + private fun determineFileType(filePath: String): String { + return when (filePath.substringAfterLast('.')) { + "csv" -> "text/csv" + "xls" -> "application/vnd.ms-excel" + else -> "application/octet-stream" + } + } + + private fun uploadBusinessPartnerRecordAndShare(file: MockMultipartFile) { + gateClient.partnerUpload.uploadPartnerCsvFile(file) + goldenRecordTaskService.createTasksForReadyBusinessPartners() + } + + private fun BusinessPartnerInputRequest.fastCopy(externalId: String, shortName: String) = + copy(externalId = externalId, legalEntity = legalEntity.copy(shortName = shortName)) + +} diff --git a/bpdm-gate/src/test/resources/testData/empty_partner_data.csv b/bpdm-gate/src/test/resources/testData/empty_partner_data.csv new file mode 100644 index 000000000..e69de29bb diff --git a/bpdm-gate/src/test/resources/testData/invalid_partner_data.csv b/bpdm-gate/src/test/resources/testData/invalid_partner_data.csv new file mode 100644 index 000000000..91b12a79e --- /dev/null +++ b/bpdm-gate/src/test/resources/testData/invalid_partner_data.csv @@ -0,0 +1,6 @@ +externalId,nameParts1,nameParts2,nameParts3,nameParts4,identifiers.type,identifiers.value,identifiers.issuingBody,states.validFrom,states.validTo,states.type,roles,isOwnCompanyData,legalEntity.legalEntityBpn,legalEntity.legalName,legalEntity.shortName,legalEntity.legalForm,legalEntity.states.validFrom,legalEntity.states.validTo,legalEntity.states.type,site.siteBpn,site.name,site.states.validFrom,site.states.validTo,site.states.type,address.addressBpn,address.name,address.addressType,address.physicalPostalAddress.geographicCoordinates.longitude,address.physicalPostalAddress.geographicCoordinates.latitude,address.physicalPostalAddress.geographicCoordinates.altitude,address.physicalPostalAddress.country,address.physicalPostalAddress.administrativeAreaLevel1,address.physicalPostalAddress.administrativeAreaLevel2,address.physicalPostalAddress.administrativeAreaLevel3,address.physicalPostalAddress.postalCode,address.physicalPostalAddress.city,address.physicalPostalAddress.district,address.physicalPostalAddress.street.namePrefix,address.physicalPostalAddress.street.additionalNamePrefix,address.physicalPostalAddress.street.name,address.physicalPostalAddress.street.nameSuffix,address.physicalPostalAddress.street.additionalNameSuffix,address.physicalPostalAddress.street.houseNumber,address.physicalPostalAddress.street.houseNumberSupplement,address.physicalPostalAddress.street.milestone,address.physicalPostalAddress.street.direction,address.physicalPostalAddress.companyPostalCode,address.physicalPostalAddress.industrialZone,address.physicalPostalAddress.building,address.physicalPostalAddress.floor,address.physicalPostalAddress.door,address.alternativePostalAddress.geographicCoordinates.longitude,address.alternativePostalAddress.geographicCoordinates.latitude,address.alternativePostalAddress.geographicCoordinates.altitude,address.alternativePostalAddress.country,address.alternativePostalAddress.administrativeAreaLevel1,address.alternativePostalAddress.postalCode,address.alternativePostalAddress.city,address.alternativePostalAddress.deliveryServiceType,address.alternativePostalAddress.deliveryServiceQualifier,address.alternativePostalAddress.deliveryServiceNumber,address.states.validForm,address.states.validTo,address.states.type +external-1,namePart1-1,namePart1-2,namePart1-3,namePart1-4,type1,value1,issuingBody1,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,SUPPLIER,TRUE,BPNL00000001DEVT,LegalEntityName1,ShortName1,LegalForm1,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNS00000001DEVT,SiteName1,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNA00000001DEVT,AddressName1,LegalAndSiteMainAddress,10.1234,20.5678,100,DE,AdminLevel1-1,AdminLevel1-2,AdminLevel1-3,123456,City1,District1,Prefix1,AdditionalPrefix1,StreetName1,Suffix1,AdditionalSuffix1,123,Supplement1,Milestone1,Direction1,CompanyPostalCode1,IndustrialZone1,Building1,Floor1,Door1,10.1111,20.2222,150,DE,AdminLevel2-1,654321,City1,PO-BOX,DeliveryQualifier1,DeliveryNumber1,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE +external-2,namePart1-1,namePart1-2,namePart1-3,namePart1-4,type2,value2,issuingBody2,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,SUPPLIER,TRUE,BPNL00000001DEVT,LegalEntityName2,ShortName2,LegalForm2,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNS00000001DEVT,SiteName2,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNA00000001DEVT,AddressName2,LegalAndSiteMainAddress,10.1234,20.5678,100,DE,AdminLevel1-1,AdminLevel1-2,AdminLevel1-3,123457,City2,District2,Prefix2,AdditionalPrefix2,StreetName2,Suffix2,AdditionalSuffix2,124,Supplement2,Milestone2,Direction2,CompanyPostalCode2,IndustrialZone2,Building2,Floor2,Door2,10.1111,20.2222,150,DE,AdminLevel2-2,654322,City2,PO-BOX,DeliveryQualifier2,DeliveryNumber2,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE +,namePart1-1,namePart1-2,namePart1-3,namePart1-4,type3,value3,issuingBody3,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,SUPPLIER,TRUE,BPNL00000001DEVT,LegalEntityName3,ShortName3,LegalForm3,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNS00000001DEVT,SiteName3,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNA00000001DEVT,AddressName3,LegalAndSiteMainAddress,10.1234,20.5678,100,DE,AdminLevel1-1,AdminLevel1-2,AdminLevel1-3,123458,City3,District3,Prefix3,AdditionalPrefix3,StreetName3,Suffix3,AdditionalSuffix3,125,Supplement3,Milestone3,Direction3,CompanyPostalCode3,IndustrialZone3,Building3,Floor3,Door3,10.1111,20.2222,150,DE,AdminLevel2-3,654323,City3,PO-BOX,DeliveryQualifier3,DeliveryNumber3,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE +external-4,namePart1-1,namePart1-2,namePart1-3,namePart1-4,type4,value4,issuingBody4,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,TEST,TRUE,BPNL00000001DEVT,LegalEntityName4,ShortName4,LegalForm4,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNS00000001DEVT,SiteName4,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNA00000001DEVT,AddressName4,LegalAndSiteMainAddress,10.1234,20.5678,100,DE,AdminLevel1-1,AdminLevel1-2,AdminLevel1-3,123459,City4,District4,Prefix4,AdditionalPrefix4,StreetName4,Suffix4,AdditionalSuffix4,126,Supplement4,Milestone4,Direction4,CompanyPostalCode4,IndustrialZone4,Building4,Floor4,Door4,10.1111,20.2222,150,DE,AdminLevel2-4,654324,City4,PO-BOX,DeliveryQualifier4,DeliveryNumber4,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE +external-1,namePart1-1,namePart1-2,namePart1-3,namePart1-4,type5,value5,issuingBody5,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,SUPPLIER,TRUE,BPNL00000001DEVT,LegalEntityName5,ShortName5,LegalForm5,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNS00000001DEVT,SiteName5,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE,BPNA00000001DEVT,AddressName5,LegalAndSiteMainAddress,10.1234,20.5678,100,DE,AdminLevel1-1,AdminLevel1-2,AdminLevel1-3,123460,City5,District5,Prefix5,AdditionalPrefix5,StreetName5,Suffix5,AdditionalSuffix5,127,Supplement5,Milestone5,Direction5,CompanyPostalCode5,IndustrialZone5,Building5,Floor5,Door5,10.1111,20.2222,150,DE,AdminLevel2-5,654325,City5,PO-BOX,DeliveryQualifier5,DeliveryNumber5,2024-05-01T00:00:00Z,2024-05-31T00:00:00Z,ACTIVE diff --git a/bpdm-gate/src/test/resources/testData/non_csv_partner_data.xls b/bpdm-gate/src/test/resources/testData/non_csv_partner_data.xls new file mode 100644 index 000000000..246a3a358 Binary files /dev/null and b/bpdm-gate/src/test/resources/testData/non_csv_partner_data.xls differ diff --git a/bpdm-gate/src/test/resources/testData/valid_partner_data.csv b/bpdm-gate/src/test/resources/testData/valid_partner_data.csv new file mode 100644 index 000000000..52f25ee7e --- /dev/null +++ b/bpdm-gate/src/test/resources/testData/valid_partner_data.csv @@ -0,0 +1,3 @@ +externalId,nameParts1,nameParts2,nameParts3,nameParts4,identifiers1.type,identifiers1.value,identifiers1.issuingBody,identifiers2.type,identifiers2.value,identifiers2.issuingBody,identifiers3.type,identifiers3.value,identifiers3.issuingBody,states1.validFrom,states1.validTo,states1.type,states2.validFrom,states2.validTo,states2.type,roles,isOwnCompanyData,legalEntity.legalEntityBpn,legalEntity.legalName,legalEntity.shortName,legalEntity.legalForm,legalEntity.states.validFrom,legalEntity.states.validTo,legalEntity.states.type,site.siteBpn,site.name,site.states.validFrom,site.states.validTo,site.states.type,address.addressBpn,address.name,address.addressType,address.physicalPostalAddress.geographicCoordinates.longitude,address.physicalPostalAddress.geographicCoordinates.latitude,address.physicalPostalAddress.geographicCoordinates.altitude,address.physicalPostalAddress.country,address.physicalPostalAddress.administrativeAreaLevel1,address.physicalPostalAddress.administrativeAreaLevel2,address.physicalPostalAddress.administrativeAreaLevel3,address.physicalPostalAddress.postalCode,address.physicalPostalAddress.city,address.physicalPostalAddress.district,address.physicalPostalAddress.street.namePrefix,address.physicalPostalAddress.street.additionalNamePrefix,address.physicalPostalAddress.street.name,address.physicalPostalAddress.street.nameSuffix,address.physicalPostalAddress.street.additionalNameSuffix,address.physicalPostalAddress.street.houseNumber,address.physicalPostalAddress.street.houseNumberSupplement,address.physicalPostalAddress.street.milestone,address.physicalPostalAddress.street.direction,address.physicalPostalAddress.companyPostalCode,address.physicalPostalAddress.industrialZone,address.physicalPostalAddress.building,address.physicalPostalAddress.floor,address.physicalPostalAddress.door,address.alternativePostalAddress.geographicCoordinates.longitude,address.alternativePostalAddress.geographicCoordinates.latitude,address.alternativePostalAddress.geographicCoordinates.altitude,address.alternativePostalAddress.country,address.alternativePostalAddress.administrativeAreaLevel1,address.alternativePostalAddress.postalCode,address.alternativePostalAddress.city,address.alternativePostalAddress.deliveryServiceType,address.alternativePostalAddress.deliveryServiceQualifier,address.alternativePostalAddress.deliveryServiceNumber,address.states.validForm,address.states.validTo,address.states.type +external-1,Business Partner Name,Company ABC AG,Another Organisation Corp,Catena Test Name,VAT_DE,DE123456789,Agency X,VAT_US,US123456789,Body Y,VAT_FR,FR123456789,,2020-01-01T00:00:00Z,2021-01-01T00:00:00Z,ACTIVE,2019-01-01T00:00:00Z,2022-01-01T00:00:00Z,INACTIVE,SUPPLIER,TRUE,BPNL0000000000XY,Limited Liability Company Name,short1,Limited Liability Company,,,,,Site Name,,,,BPNA0000000001XY,Address Name,LegalAddress,7.619,45.976,4478,US,adminAreaLevel1RegionCode_2, Fulton County,,70547,Atlanta,TODO,,,TODO,,,,B,,direction1,,Industrial Zone Two,Building Two,Floor Two,Door Two,7.619,45.976,4478,DE,adminAreaLevel1RegionCode_2,70547,Stuttgart,PO_BOX,DHL,1234,,, +external-2,Business Partner Name,Company ABC AG,Another Organisation Corp,Catena Test Name,VAT_DE,DE123456789,Agency X,VAT_US,US123456789,Body Y,VAT_FR,FR123456789,,2020-01-01T00:00:00Z,2021-01-01T00:00:00Z,ACTIVE,2019-01-01T00:00:00Z,2022-01-01T00:00:00Z,INACTIVE,SUPPLIER,TRUE,BPNL0000000000XY,Limited Liability Company Name,2,Limited Liability Company,,,,,Site Name,,,,BPNA0000000001XY,Address Name,LegalAddress,7.619,45.976,4478,US,adminAreaLevel1RegionCode_2, Fulton County,,70547,Atlanta,TODO,,,TODO,,,,B,,direction1,,Industrial Zone Two,Building Two,Floor Two,Door Two,7.619,45.976,4478,DE,adminAreaLevel1RegionCode_2,70547,Stuttgart,PO_BOX,DHL,1234,,, \ No newline at end of file diff --git a/charts/bpdm/Chart.yaml b/charts/bpdm/Chart.yaml index 82738453b..2f586bca3 100644 --- a/charts/bpdm/Chart.yaml +++ b/charts/bpdm/Chart.yaml @@ -22,7 +22,7 @@ apiVersion: v2 name: bpdm type: application description: A Helm chart for Kubernetes that deploys the BPDM applications -version: 5.0.2-SNAPSHOT +version: 5.0.3-SNAPSHOT appVersion: "6.0.2-SNAPSHOT" home: https://github.com/eclipse-tractusx/bpdm sources: diff --git a/charts/bpdm/templates/keycloakRealm.tpl b/charts/bpdm/templates/keycloakRealm.tpl index 703db43cf..e34036d90 100644 --- a/charts/bpdm/templates/keycloakRealm.tpl +++ b/charts/bpdm/templates/keycloakRealm.tpl @@ -385,6 +385,13 @@ roles: clientRole: true containerId: 0562ecfa-f17b-4d32-86cc-061f7da34b6b attributes: {} + - id: e3ca0b50-95c7-43d5-baf6-359d87fc272a + name: upload_input_partner + description: Upload access to business partner input data + composite: false + clientRole: true + containerId: 0562ecfa-f17b-4d32-86cc-061f7da34b6b + attributes: {} account-console: [] EDC-GATE-OUTPUT-CONSUMER: [] broker: @@ -549,6 +556,7 @@ groups: - write_input_partner - read_sharing_state - read_input_partner + - upload_input_partner subGroups: [] - id: d0cbde40-4896-4484-bff4-8666d2ad1a14 name: Input Consumer @@ -575,6 +583,7 @@ groups: - write_input_partner - read_sharing_state - read_input_partner + - upload_input_partner subGroups: [] - id: a66cad54-3ff3-4673-88ea-304892073e13 name: Output Consumer diff --git a/pom.xml b/pom.xml index 05a3cca92..088b91218 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,7 @@ 0.8.12 1.19.8 1.1.20 + 5.9 @@ -201,6 +202,13 @@ 3.2.0 test + + + + com.opencsv + opencsv + ${opencsv.version} +