Skip to content

Commit

Permalink
feat(Gate): add business partner relations
Browse files Browse the repository at this point in the history
- add new endpoints to search, create, update and delete business partner relations
- add logic for CRUD and constraint checking
- add database table for relations
- add tests
  • Loading branch information
nicoprow committed Dec 20, 2024
1 parent 72b142e commit 636f67c
Show file tree
Hide file tree
Showing 56 changed files with 4,834 additions and 265 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ For changes to the BPDM Helm charts please consult the [changelog](charts/bpdm/C
- BPDM Pool: Post endpoint to fetch the BPNL/S/A based on the requested identifiers.([#1052](https://github.com/eclipse-tractusx/bpdm/issues/1052))
- BPDM Gate & Orchestrator: Enhance the error handling mechanism for the orchestrator and gate components by extending the list of available error codes.([#1003](https://github.com/eclipse-tractusx/bpdm/pull/1003#pullrequestreview-2477395867))
- BPDM System Test: Tester module which performs automated end-to-end tests on an existing golden record process.([#1070](https://github.com/eclipse-tractusx/bpdm/pull/1070))
- BPDM Gate: Add endpoints for managing business partner relations ([#1027](https://github.com/eclipse-tractusx/bpdm/issues/1027))

### Changed

Expand Down
12 changes: 6 additions & 6 deletions DEPENDENCIES
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ maven/mavencentral/io.cucumber/cucumber-spring/7.18.1, MIT, approved, clearlydef
maven/mavencentral/io.cucumber/datatable/7.18.1, Apache-2.0, approved, #15145
maven/mavencentral/io.cucumber/docstring/7.18.1, MIT, approved, clearlydefined
maven/mavencentral/io.cucumber/gherkin/28.0.0, MIT, approved, #14276
maven/mavencentral/io.cucumber/html-formatter/21.4.1, MIT, restricted, clearlydefined
maven/mavencentral/io.cucumber/html-formatter/21.4.1, Apache-2.0 AND MIT, approved, #17770
maven/mavencentral/io.cucumber/junit-xml-formatter/0.5.0, MIT, approved, clearlydefined
maven/mavencentral/io.cucumber/messages/24.1.0, MIT, approved, #14274
maven/mavencentral/io.cucumber/query/12.2.0, MIT, approved, clearlydefined
Expand Down Expand Up @@ -128,7 +128,7 @@ maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.33, Apache-2.0
maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.33, Apache-2.0, approved, #6997
maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.33, Apache-2.0, approved, #7920
maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, #17641
maven/mavencentral/org.aspectj/aspectjweaver/1.9.22.1, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #15252
maven/mavencentral/org.aspectj/aspectjweaver/1.9.22.1, EPL-1.0, approved, tools.aspectj
maven/mavencentral/org.assertj/assertj-core/3.24.2, Apache-2.0, approved, #6161
maven/mavencentral/org.awaitility/awaitility/4.2.2, Apache-2.0, approved, #14178
maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined
Expand All @@ -150,7 +150,7 @@ maven/mavencentral/org.glassfish.jaxb/jaxb-xjc/4.0.5, BSD-3-Clause, approved, ee
maven/mavencentral/org.glassfish.jaxb/txw2/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl
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, None, restricted, #17677
maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-3-Clause, approved, #17677
maven/mavencentral/org.hdrhistogram/HdrHistogram/2.2.2, BSD-2-Clause AND CC0-1.0 AND CC0-1.0, approved, #14828
maven/mavencentral/org.hibernate.common/hibernate-commons-annotations/7.0.3.Final, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.hibernate.orm/hibernate-core/6.6.2.Final, (EPL-2.0 OR BSD-3-Clause) AND LGPL-2.1-or-later AND MIT, approved, #17553
Expand All @@ -169,8 +169,8 @@ maven/mavencentral/org.jboss.shrinkwrap/shrinkwrap-spi/1.2.6, Apache-2.0, approv
maven/mavencentral/org.jboss/jandex/2.4.3.Final, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.jetbrains.kotlin/kotlin-reflect/2.1.0, Apache-2.0, approved, #17637
maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-common/1.8.0, Apache-2.0, approved, #8910
maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk7/2.1.0, None, restricted, #17633
maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk8/2.1.0, None, restricted, #17635
maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk7/2.1.0, Apache-2.0, approved, #17633
maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk8/2.1.0, Apache-2.0, approved, #17635
maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/2.1.0, Apache-2.0, approved, #17634
maven/mavencentral/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.8.1, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.8.1, Apache-2.0, approved, clearlydefined
Expand Down Expand Up @@ -251,7 +251,7 @@ maven/mavencentral/org.testcontainers/jdbc/1.20.4, Apache-2.0, approved, #16621
maven/mavencentral/org.testcontainers/junit-jupiter/1.20.4, MIT, approved, #16552
maven/mavencentral/org.testcontainers/postgresql/1.20.4, MIT, approved, #16627
maven/mavencentral/org.testcontainers/testcontainers/1.20.4, MIT, approved, #15747
maven/mavencentral/org.webjars/swagger-ui/5.18.2, Apache-2.0 AND (Apache-2.0 AND MIT) AND MIT AND DOC AND LicenseRef-scancode-proprietary-license, restricted, #17636
maven/mavencentral/org.webjars/swagger-ui/5.18.2, Apache-2.0, approved, #17636
maven/mavencentral/org.webjars/webjars-locator-lite/1.0.1, MIT, approved, clearlydefined
maven/mavencentral/org.xmlunit/xmlunit-core/2.10.0, Apache-2.0, approved, #14590
maven/mavencentral/org.yaml/snakeyaml/2.3, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #16046
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ package org.eclipse.tractusx.bpdm.test.containers

import dasniko.testcontainers.keycloak.KeycloakContainer
import org.eclipse.tractusx.bpdm.test.config.SelfClientConfigProperties
import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer.Companion.TENANT_BPNL
import org.eclipse.tractusx.bpdm.test.containers.KeyCloakInitializer.Companion.keycloakContainer
import org.keycloak.representations.idm.ClientRepresentation
import org.keycloak.representations.idm.ProtocolMapperRepresentation
import org.keycloak.representations.idm.UserRepresentation
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.context.ApplicationContextInitializer
import org.springframework.context.ConfigurableApplicationContext
Expand Down Expand Up @@ -105,17 +108,42 @@ abstract class CreateNewSelfClientInitializer: SelfClientInitializer(){

val createdClientUuid = clients.findByClientId(clientId).first().id


val newProtocolMapper = ProtocolMapperRepresentation().apply {
name = "BPN"
protocol = "openid-connect"
protocolMapper = "oidc-usermodel-attribute-mapper"
config = mapOf(
"introspection.token.claim" to "true",
"userinfo.token.claim" to "true",
"user.attribute" to "bpn",
"id.token.claim" to "true",
"access.token.claim" to "true",
"claim.name" to "bpn",
"jsonType.label" to "String"
)
}
clients
.get(createdClientUuid)
.protocolMappers
.createMapper(newProtocolMapper)

val newServiceAccount = clients
.get(createdClientUuid)
.serviceAccountUser

newServiceAccount.attributes = mutableMapOf(Pair("bpn", listOf(TENANT_BPNL)))

realm.users()
.get(newServiceAccount.id)
.update(newServiceAccount)

realm.users()
.get(newServiceAccount.id)
.roles()
.clientLevel(roleManagementClient.toRepresentation().id)
.add(listOf(role))


super.initialize(applicationContext)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.bpdm.test.system.utils
package org.eclipse.tractusx.bpdm.test.testdata.gate

import com.neovisionaries.i18n.CountryCode
import org.eclipse.tractusx.bpdm.common.dto.AddressType
Expand All @@ -30,15 +30,14 @@ import org.eclipse.tractusx.bpdm.gate.api.model.request.BusinessPartnerInputRequ
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.test.system.config.TestRunData
import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset
import kotlin.random.Random

class GateInputFactory(
private val testMetadata: TestMetadata,
private val testRunData: TestRunData
private val testRunData: TestRunData?
) {
val genericFullValidWithSiteWithoutAnyBpn = createAllFieldsFilled("genericFullValidWithSiteWithoutAnyBpn")
{ it.withoutAnyBpn().withAddressType(null) }
Expand All @@ -48,7 +47,7 @@ class GateInputFactory(
}

fun createFullValid(seed: String, externalId: String = seed): BusinessPartnerInputRequest {
return SeededTestDataCreator(seed).createAllFieldsFilled().copy(externalId = testRunData.toExternalId(externalId))
return SeededTestDataCreator(seed).createAllFieldsFilled().copy(externalId = testRunData?.toExternalId(externalId) ?: externalId)
}

inner class SeededTestDataCreator(
Expand All @@ -62,7 +61,7 @@ class GateInputFactory(
fun createAllFieldsFilled(): BusinessPartnerInputRequest{

return BusinessPartnerInputRequest(
externalId = "${seed}_${testRunData.testTime}",
externalId = testRunData?.let { "${seed}_${testRunData.testTime}" } ?: seed ,
nameParts = listRange.map { "Name Part $seed $it" },
identifiers = emptyList(),
roles = BusinessPartnerRole.entries,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/

package org.eclipse.tractusx.bpdm.test.system.config
package org.eclipse.tractusx.bpdm.test.testdata.gate

import java.time.Instant

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@ import java.util.stream.Collectors
class CustomJwtAuthenticationConverter(private val resourceId: String, private val requiredBpn: String = "") : Converter<Jwt, AbstractAuthenticationToken> {
private val defaultGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
override fun convert(source: Jwt): AbstractAuthenticationToken {
val authorities: Collection<GrantedAuthority> =
defaultGrantedAuthoritiesConverter.convert(source)!!.plus(extractResourceRoles(source, resourceId, requiredBpn)).toSet()

val bpn = source.claims["bpn"] as String? ?: ""
val tokenAttributes = mutableMapOf<String, Any>("bpn" to bpn)

val authorities: Collection<GrantedAuthority> =
defaultGrantedAuthoritiesConverter.convert(source)!!
.plus(extractResourceRoles(source, resourceId, requiredBpn))
.let { if(bpn.isNotBlank()) it.plus(SimpleGrantedAuthority(bpn)) else it }
.toSet()

return JwtAuthenticationToken(source, authorities, tokenAttributes.toString())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ import org.springframework.web.bind.annotation.ResponseStatus


@ResponseStatus(HttpStatus.BAD_REQUEST)
class BpdmValidationErrorException(
open class BpdmValidationErrorException(
val validationErrors: List<ValidationError>
): RuntimeException(validationErrors.joinToString())
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ 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.eclipse.tractusx.bpdm.gate.api.model.response.GateErrorResponse
import org.springframework.core.io.ByteArrayResource
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
Expand Down Expand Up @@ -55,7 +55,7 @@ interface GatePartnerUploadApi {
ApiResponse(responseCode = "400", description = "On malformed Business partner upload request",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = PartnerUploadErrorResponse::class)
schema = Schema(implementation = GateErrorResponse::class)
)]),
]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*******************************************************************************
* 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 jakarta.validation.Valid
import org.eclipse.tractusx.bpdm.common.dto.PageDto
import org.eclipse.tractusx.bpdm.common.dto.PaginationRequest
import org.eclipse.tractusx.bpdm.gate.api.GateRelationApi.Companion.RELATIONS_PATH
import org.eclipse.tractusx.bpdm.gate.api.model.RelationDto
import org.eclipse.tractusx.bpdm.gate.api.model.RelationType
import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPostRequest
import org.eclipse.tractusx.bpdm.gate.api.model.request.RelationPutRequest
import org.eclipse.tractusx.bpdm.gate.api.model.response.GateErrorResponse
import org.springdoc.core.annotations.ParameterObject
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.*
import java.time.Instant

@RequestMapping(RELATIONS_PATH, produces = [MediaType.APPLICATION_JSON_VALUE])
interface GateRelationApi {

companion object{
const val RELATIONS_PATH = "${ApiCommons.BASE_PATH}/input/business-partner-relations"
}

@Operation(
summary = "Find business partner input relations",
description = "Find paginated list of business partner relations from the input stage. " +
"There are various filter criteria available. " +
"All filters are 'AND' filters."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "A paginated list of business partner relations for the input stage")
]
)
@GetMapping
fun get(
@Schema(description = "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication.")
@RequestParam tenantBpnL: String? = null,
@Schema(description = "Only show relations with the given external identifiers")
@RequestParam externalIds: List<String>? = null,
@Schema(description = "Only show relations of the given type")
@RequestParam relationType: RelationType? = null,
@Schema(description = "Only show relations which have the given business partners as sources")
@RequestParam businessPartnerSourceExternalIds: List<String>? = null,
@Schema(description = "Only show relations which have the given business partners as targets")
@RequestParam businessPartnerTargetExternalIds: List<String>? = null,
@Schema(description = "Only show relations which have been modified after the given time stamp")
@RequestParam updatedAtFrom: Instant? = null,
@ParameterObject @Valid paginationRequest: PaginationRequest = PaginationRequest()
): PageDto<RelationDto>

@Operation(
summary = "Create a new business partner input relation",
description = "Create a new relation between two business partner entries on the input stage. " +
"The external identifier is optional and a new one will be automatically created if not given. " +
"A given external identifier has to be unique."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "201", description = "The created business partner input relation"),
ApiResponse(responseCode = "400", description = "If the business partner could not be created based on wrong or insufficient data provided such as non-existent business partners or violated relation constraints. ",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)]),
ApiResponse(responseCode = "409", description = "If a relation with the given external identifier already exists",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)]),
]
)
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
fun post(
@RequestBody requestBody: RelationPostRequest
): RelationDto

@Operation(
summary = "Update a business partner input relation",
description = "Update an existing business partner relation on the input stage. " +
"By using a request parameter it is also possible to create a relation if the relation with the given external identifier does not exist. "
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "The given business partner relation has been updated."),
ApiResponse(responseCode = "400", description = "On wrong or insufficient user provided data like references to non-existent business partners or relations that violate the relation constraints. ",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)])
]
)
@PutMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
fun put(
@Schema(description = "If true a business partner relation will be created even if a relation could not be found under the given external identifier.")
@RequestParam createIfNotExist: Boolean = false,
@RequestBody requestBody: RelationPutRequest
): RelationDto

@Operation(
summary = "Delete an existing business partner relation",
description = "Delete a relation between two business partners on the input stage."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "The specified relation has been deleted."),
ApiResponse(responseCode = "400", description = "On specifying a relation that does not exist. ",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = GateErrorResponse::class)
)])
]
)
@DeleteMapping
fun delete(
@Schema(description = "The BPNL of the tenant to search in. If not given, the system attempts to infer the tenant from the authentication.")
@RequestParam tenantBpnL: String? = null,
@Schema(description = "The external identifier of the business partner relation to delete")
@RequestParam externalId: String
)


}
Loading

0 comments on commit 636f67c

Please sign in to comment.