Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 9424c8cd authored by Alessandro Padula's avatar Alessandro Padula
Browse files

Merge branch 'develop' into 'main'

From develop into main

See merge request !15
parents a1c1337e 1fcb9efb
Branches
Tags v1.2.0
1 merge request!15From develop into main
Pipeline #320500 failed
Showing
with 1008 additions and 293 deletions
@echo off
setlocal enabledelayedexpansion
rem Set the Maven arguments for running the application
set "MVN_ARGUMENTS=-Dspring-boot.run.profiles=consumer,provider -Dspring-boot.run.arguments=--kafka.fatal-if-broker-not-available=false"
rem Define OpenAPI documentation variables
rem Define the base URL
set "BASE_URL=http://localhost:8080"
rem Define openapi count based on the number of files
set "API_COUNT=1"
rem Define openapi files and endpoints
set "API_FILES[0]=openapi-v1.json"
set "API_ENDPOINTS[0]=/api-docs/v1"
ECHO.
ECHO ============================
ECHO Select operation to execute:
ECHO ============================
ECHO 1. Run only Spotless (code formatting)
ECHO 2. Run only build (mvn clean package)
ECHO 3. Start Application and Generate API docs (to generate openapi json files)
ECHO 4. Processing API docs (to make OpenAPI files compliant with ITB)
ECHO 5. Run all operations (required for the release)
ECHO ============================
ECHO.
SET /P CHOICE=Enter the number of the desired operation:
IF "%CHOICE%"=="1" SET "ONLY_ONE=true" && GOTO SPOTLESS
IF "%CHOICE%"=="2" SET "ONLY_ONE=true" && GOTO BUILD
IF "%CHOICE%"=="3" SET "ONLY_ONE=true" && GOTO START_APP
IF "%CHOICE%"=="4" SET "ONLY_ONE=true" && GOTO PROCESSING_DOCS
IF "%CHOICE%"=="5" SET "ONLY_ONE=false" && GOTO ALL
ECHO.
ECHO Invalid choice. Exiting.
EXIT /B 1
:ALL
ECHO ============================
ECHO Executing all operations...
ECHO ============================
:SPOTLESS
ECHO ============================
ECHO Formatting code with Spotless...
ECHO ============================
call mvn spotless:apply || exit /B 1
if "%ONLY_ONE%"=="true" GOTO END
:BUILD
ECHO ============================
ECHO Building the application...
ECHO ============================
call mvn clean package || exit /B 1
if "%ONLY_ONE%"=="true" GOTO END
:START_APP
ECHO ============================
ECHO Starting the application...
ECHO ============================
start "Spring Boot App" cmd /c "call mvn spring-boot:run"
start "Spring Boot App" cmd /k "call mvn spring-boot:run %MVN_ARGUMENTS%"
ECHO ============================
ECHO Waiting for the application to start...
ECHO ============================
set "HEALTH_URL=http://localhost:8080/actuator/health/liveness"
set "MAX_ATTEMPTS=30"
set /A "COUNT=0"
......@@ -43,12 +88,25 @@ ECHO ============================
exit /B 1
:GENERATE_DOCS
ECHO ============================
ECHO Generating OpenAPI documentation...
set /a LAST_INDEX=!API_COUNT!-1
for /L %%i in (0,1,%LAST_INDEX%) do (
echo Download !API_FILES[%%i]! from !BASE_URL!!API_ENDPOINTS[%%i]!
curl -o !API_FILES[%%i]! !BASE_URL!!API_ENDPOINTS[%%i]! || exit /B 1
)
ECHO OpenAPI JSON files generated successfully.
if "%ONLY_ONE%"=="true" GOTO END
:PROCESSING_DOCS
ECHO ============================
ECHO Processing OpenAPI files...
ECHO ============================
call mvn springdoc-openapi:generate || exit /B 1
ECHO.
powershell -ExecutionPolicy Bypass -File #process_openapi.ps1
if "%ONLY_ONE%"=="true" GOTO END
:END
ECHO.
ECHO ============================
ECHO Process completed!
ECHO ============================
pause
Get-ChildItem -Filter "openapi-v*.json" | ForEach-Object {
$file = $_.FullName
Write-Host "Processing $file..."
$json = Get-Content $file -Raw | ConvertFrom-Json
# Extract version from file name or servers.url field
$versionFound = $false
$version = $null
# Check the file name
if ($file -match "-v(\d+)\.json$") {
$version = "/v$($Matches[1])"
$versionFound = $true
# Write-Host "→ Version found in filename: $version"
}
# If not found in filename, look in servers.url
if (-not $versionFound -and $json.servers) {
foreach ($server in $json.servers) {
if ($server.url -match "/v(\d+)(?:/|$)") {
$version = "/v$($Matches[1])"
$versionFound = $true
# Write-Host "→ Version found in servers.url: $version"
break
}
}
}
# Ensure servers.url includes the version
if ($json.servers) {
foreach ($server in $json.servers) {
if (-not ($server.url -like "*$version")) {
$oldUrl = $server.url
$server.url = $server.url.TrimEnd("/") + $version
# Write-Host "→ Updated server URL from '$oldUrl' to '$($server.url)'"
}
}
}
# Simplest approach: directly remove version from paths using replace
$pathsModified = $false
$newPaths = @{}
$versionToRemove = "/v\d+" # Version pattern to remove (/v1, /v2, etc.)
# Write-Host "Removing '$versionToRemove' version from paths"
foreach ($pathKey in $json.paths.PSObject.Properties.Name) {
# Write-Host "→ Processing path: $pathKey"
# First remove the /api prefix if present
$newPathKey = $pathKey -replace "^$apiPrefix", ""
# Then remove the version pattern /vX if present
$newPathKey = $newPathKey -replace "$versionToRemove", ""
if ($newPathKey -ne $pathKey) {
$pathsModified = $true
# Write-Host " ✓ Path modified: $pathKey -> $newPathKey"
} else {
# Write-Host " ✓ Path unchanged: $pathKey"
}
# Copy all operations and details from original path
$newPaths[$newPathKey] = $json.paths.$pathKey
}
# Update paths only if modified
if ($pathsModified) {
$json.paths = $newPaths
# Write-Host "→ Paths updated with new keys"
} else {
# Write-Host "→ No path modifications needed"
}
# Find the appropriate output directory (static/openapi) in the project
$outputDir = $null
# Try to find the static/openapi directory in standard Spring structure
$possiblePaths = @(
"src/main/resources/static/openapi"
)
foreach ($path in $possiblePaths) {
$fullPath = Join-Path (Split-Path -Parent $PSScriptRoot) $path
if (Test-Path $fullPath) {
$outputDir = $fullPath
break
}
# Also check if the path exists relative to current directory
$relPath = Join-Path $PSScriptRoot $path
if (Test-Path $relPath) {
$outputDir = $relPath
break
}
}
# If we couldn't find the static/openapi directory, create it
if (-not $outputDir) {
$outputDir = Join-Path $PSScriptRoot "src/main/resources/static/openapi"
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
Write-Host "→ Created directory: $outputDir"
}
}
# Generate output filename
$fileName = [System.IO.Path]::GetFileNameWithoutExtension($file)
$outFile = Join-Path $outputDir "$($fileName).json"
# Fix JSON formatting issue - Convert to string first and then save to file
$jsonString = ($json | ConvertTo-Json -Depth 100)
[System.IO.File]::WriteAllText($outFile, $jsonString, [System.Text.UTF8Encoding]::new($false))
# Write-Host "→ File saved: $outFile"
}
Write-Host "OpenAPI files processed successfully."
Write-Host "Saved in src/main/resources/static/openapi"
This diff is collapsed.
This diff is collapsed.
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
log4j2.xml: |
<Configuration status="WARN" monitorInterval="{{ .Values.log4j.monitorInterval }}">
<CustomLevels>
<CustomLevel name="BUSINESS" intLevel="250"/>
</CustomLevels>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<JsonTemplateLayout eventTemplateUri="classpath:log4j2-template.json"/>
</Console>
</Appenders>
<Loggers>
<Root level="{{ .Values.log4j.level.root }}" includeLocation="true">
<AppenderRef ref="Console"/>
</Root>
<Logger name="eu.europa" level="{{ .Values.log4j.level.app }}" includeLocation="true" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="feign" level="{{ .Values.log4j.level.feign }}" includeLocation="true" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
</Loggers>
</Configuration>
......@@ -11,10 +11,34 @@ spec:
app: {{ .Release.Name }}
template:
metadata:
{{- if eq .Values.profile "consumer" }}
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: {{ .Values.vault.role }}
{{- if and (hasKey .Values "vault") (hasKey .Values.vault "service") }}
vault.hashicorp.com/service: {{ .Values.vault.service }}
{{- end }}
vault.hashicorp.com/agent-inject-secret-config.txt: "{{ tpl .Values.vault.secretPath . }}"
vault.hashicorp.com/agent-inject-perms-config.txt: "0644"
vault.hashicorp.com/agent-inject-template-config.txt: |
{{- $secretPath := tpl .Values.vault.secretPath . -}}
{{- $template := printf (printf "{{`{{ with secret \"%s\" }}\n" $secretPath) -}}
{{- $template = printf "%s{{ range $k, $v := .Data.data }}\n" $template -}}
{{- $template = printf "%s export {{ $k }}={{ $v }}\n" $template -}}
{{- $template = printf "%s{{ end }}\n" $template -}}
{{- $template = printf "%s{{ end }}`}}" $template -}}
{{- tpl $template . | nindent 10 }}
{{- end }}
labels:
app: {{ .Release.Name }}
spec:
serviceAccountName: {{ if .Values.serviceAccount }}{{ .Values.serviceAccount.name | default .Release.Name }}{{ else }}{{ .Release.Name }}{{ end }}
volumes:
- name: {{ .Release.Name }}-configmap
configMap:
name: {{ .Release.Name }}-configmap
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
......@@ -22,13 +46,46 @@ spec:
resources: {{ toYaml .Values.resources | nindent 12 }}
ports:
- containerPort: {{ .Values.containerPort }}
{{- if eq .Values.profile "consumer" }}
command: [ "/bin/bash" ]
args: [ "-c", "source /vault/secrets/config.txt && java -jar {{ .Values.appJarPath }}" ]
{{- end }}
volumeMounts:
- name: {{ .Release.Name }}-configmap
mountPath: /config
env:
- name: EDC_CONNECTOR_BASE_URL
value: {{ .Values.edcConnector.baseUrl | quote }}
{{- if eq .Values.profile "provider" }}
- name: EDC_CONNECTOR_TIER2_BASE_URL
value: {{ .Values.edcConnector.tier2BaseUrl | quote }}
- name: EDC_CONNECTOR_PARTICIPANT_ID
value: {{ .Values.edcConnector.participantId | quote }}
{{- end }}
{{- if or (eq .Values.profile "consumer") (eq .Values.profile "consumerNoKafka") }}
- name: EDC_CONNECTOR_API_KEY_VALUE
value: {{ .Values.edcConnector.apiKey | quote }}
{{- end }}
{{- if eq .Values.profile "consumer" }}
- name: KAFKA_TRANSFER_PROCESS_TOPIC
value: {{ .Values.kafka.topics.transferProcess | quote }}
- name: KAFKA_AUTH_TYPE
value: {{ .Values.kafka.auth.type | quote }}
- name: KAFKA_FATAL_IF_BROKER_NOT_AVAILABLE
value: {{ .Values.kafka.fatalIfBrokerNotAvailable | quote }}
{{- end }}
- name: LOGGING_CONFIG
value: {{ .Values.log4j.config | quote }}
- name: OPENAPI_CONFIG_SERVERS
value: {{ .Values.openapiConfig.servers | quote }}
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: {{ .Values.openTelemetry.otlpExporter.endpoint | quote }}
......@@ -41,12 +98,19 @@ spec:
- name: OTEL_SDK_DISABLED
value: {{ .Values.openTelemetry.disabled | quote }}
{{- if eq .Values.profile "consumer" }}
- name: SPRING_KAFKA_BOOTSTRAP_SERVERS
value: {{ .Values.kafka.bootstrapServers | quote }}
- name: SPRING_KAFKA_GROUP_ID
value: {{ .Values.kafka.consumer.groupId | quote }}
{{- end }}
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.profile | quote }}
- name: WEB_MVC_CORS_ALLOWED_ORIGINS
value: {{ .Values.web.mvc.cors.allowedOrigins | quote }}
- name: OPENAPI_CONFIG_SERVERS
value: {{ .Values.openapiConfig.servers | quote }}
livenessProbe:
httpGet:
path: /actuator/health/liveness
......
......@@ -14,6 +14,19 @@ serviceAccount:
# optional service account prefix
prefix:
log4j:
# location for the log4j configuration file as defined in log4j-configmap.yaml and deployment.yaml
config: file:/config/log4j2.xml
# config file monitoring interval for hot changes (seconds)
monitorInterval: 10
level:
# logging level for all logs excluding the specific ones
root: INFO
# logging level for application components only
app: INFO
# logging level for feign clients only
feign: INFO
# comma separated list of servers url to be used by swagger
openapiConfig:
servers: https://edc-connector-adapter.dev.simpl-europe.eu
......@@ -60,6 +73,12 @@ web:
# optional list (comma separated) of allowed paths (ant matcher expressions) to skip JWT token check (ex: actuator health paths)
allowedPaths: "/actuator/health/*"
# application profile (provider or consumer)
profile: provider
#########################################################################
# 'provider' profile only
#########################################################################
edcConnector:
# url to the provider EDC connector (internal service)
......@@ -70,3 +89,40 @@ edcConnector:
tier2BaseUrl: https://edc-provider.dev.simpl-europe.eu/protocol
# the participant id to be used as assigner in policies as expected by the EDC connector on contract negotiation
participantId: provider
#########################################################################
# 'consumer' profile only
#########################################################################
#appJarPath: "/home/simpluser/app.jar"
#edcConnector:
# # url to the provider EDC connector (internal service)
# baseUrl: https://edc-provider.dev.simpl-europe.eu
# apiKey: edc-apikey-value
#kafka:
# # urls list to KAFKA bootstrap servers (comma separed if more than one)
# bootstrapServers: localhost:9092
# # consumers group id
# consumer:
# groupId: contract_consumption
# topics:
# # topic used by transfer process
# transferProcess: contract_consumption.transfer
# # can be one of: SASL_PLAINTEXT (if blank no auth enabled)
# auth:
# type: SASL_PLAINTEXT
# # flag to stop application if kafka broker is not available
# fatalIfBrokerNotAvailable: true
#vault:
# secretEngine: "kv/data-1"
# # Dev path: kv/data-1/gaiax-edc-dev-sd-contract-consumption-be
# secretPath: "{{ .Values.vault.secretEngine }}/{{ .Release.Namespace }}-{{ .Release.Name }}"
# role: "data1"
# service: "https://vault.tools.simpl-europe.eu/"
# EDC Connector Adapter Deploy Instructions
## Required components
- Kafka broker (only if consumer profile)
- HashiCorp Vault (only if consumer profile)
- Tier1 gateway installed and configured for the provider participant: **edc-contract-adapter** must be protected by the gateway and must not be reached directly from a public address.
- EDC connector service installed for the provider participant.
- Federated catalogue service exposed by a Tier2 Gateway on the authority participant.
......@@ -28,6 +29,19 @@ domainUrlEnvIdentifier: dev
# domain associated with Dev Cluster (don't modify it)
domain: dev.simpl-europe.eu
log4j:
# location for the log4j configuration file as defined in log4j-configmap.yaml and deployment.yaml
config: file:/config/log4j2.xml
# config file monitoring interval for hot changes (seconds)
monitorInterval: 10
level:
# logging level for all logs excluding the specific ones
root: INFO
# logging level for application components only
app: INFO
# logging level for feign clients only
feign: INFO
openTelemetry:
# disables the OpenTelemetry SDK (true on local dev)
disabled: true
......@@ -79,6 +93,13 @@ web:
allowedPaths: "/actuator/health/*"
# application profile (provider or consumer)
profile: provider
#########################################################################
# 'provider' profile only
#########################################################################
edcConnector:
# url to the provider EDC connector (internal service)
baseUrl: https://edc-provider.dev.simpl-europe.eu
......@@ -89,10 +110,47 @@ edcConnector:
# the participant id to be used as assigner in policies as expected by the EDC connector on contract negotiation
participantId: provider
#########################################################################
# 'consumer' profile only
#########################################################################
#appJarPath: "/home/simpluser/app.jar"
#edcConnector:
# # url to the provider EDC connector (internal service)
# baseUrl: https://edc-provider.dev.simpl-europe.eu
# apiKey: edc-apikey-value
#kafka:
# # urls list to KAFKA bootstrap servers (comma separed if more than one)
# bootstrapServers: localhost:9092
# # consumers group id
# consumer:
# groupId: contract_consumption
# topics:
# # topic used by transfer process
# transferProcess: contract_consumption.transfer
# # can be one of: SASL_PLAINTEXT (if blank no auth enabled)
# auth:
# type: SASL_PLAINTEXT
# # flag to stop application if kafka broker is not available
# fatalIfBrokerNotAvailable: true
#vault:
# secretEngine: "kv/data-1"
# # Dev path: kv/data-1/gaiax-edc-dev-sd-contract-consumption-be
# secretPath: "{{ .Values.vault.secretEngine }}/{{ .Release.Namespace }}-{{ .Release.Name }}"
# role: "data1"
# service: "https://vault.tools.simpl-europe.eu/"
```
## Tier1 Cloud Gateway Integration
In your Tier1 Gateway values.yaml add new routes and rbac rules, example:
## Tier1 Cloud Gateway Integration (provider profile)
In the provider Tier1 Gateway values.yaml add new routes and rbac rules, example:
```yaml
routes:
......@@ -104,14 +162,67 @@ In your Tier1 Gateway values.yaml add new routes and rbac rules, example:
- StripPrefix=1
rbac:
- path: "/edc-connector-adapter/api/*/registration/register"
- path: "/edc-connector-adapter/api/registration/*/register"
method: GET
roles:
- SD_PUBLISHER
- path: "/edc-connector-adapter/api/*/configuration/participant"
- path: "/edc-connector-adapter/api/configuration/*/participant"
method: POST
roles:
- SD_PUBLISHER
```
where http://edc-connector-adapter.gaiax-edc-dev-sd.svc.cluster.local:8080 must be changed to your edc connector service service url
## Tier1 Cloud Gateway Integration (consumer profile)
In the consumer Tier1 Gateway values.yaml add new routes and rbac rules, example:
```yaml
routes:
- id: edc-connector-adapter
uri: http://edc-connector-adapter.gaiax-edc-dev-sd.svc.cluster.local:8080
predicates:
- Path=/edc-connector-adapter/**
filters:
- StripPrefix=1
rbac:
- path: "/edc-connector-adapter/api/contract/*/offers/search"
method: POST
roles:
- SD_CONSUMER
- path: "/edc-connector-adapter/api/contract/*/negotiation/start"
method: POST
roles:
- SD_CONSUMER
- path: "/edc-connector-adapter/api/contract/*/negotiation/status/**"
method: GET
roles:
- SD_CONSUMER
- path: "/edc-connector-adapter/api/transfer/*/start"
method: POST
roles:
- SD_CONSUMER
- path: "/edc-connector-adapter/api/transfer/*/status/**"
method: GET
roles:
- SD_CONSUMER
```
where http://edc-connector-adapter.gaiax-edc-dev-sd.svc.cluster.local:8080 must be changed to your edc connector service service url
## HashiCorp Vault integration (consumer profile)
1) create a Vault role for the application
2) create your Vault secrets engine
2) create a new Vault secret for **edc-connector-adapter** application named *{{ .Release.Namespace }}-{{ .Release.Name }}*
3) edit your Helm *values.yaml* file and configure:
- *vault.secretEngine*
- *vault.role*
- *vault.service*
4) adding the following keys for the required component credentials
#### Kafka credentials
```
KAFKA_AUTH_SASL_USERNAME
KAFKA_AUTH_SASL_PASSWORD
```
> if your Kakfa broker does not require any authentication set kafka.auth.type as blank in Helm values.yaml file
\ No newline at end of file
{"openapi":"3.0.1","info":{"title":"EDC Connector Adapter Application API","description":"OpenApi documentation for the EDC Connector Adapter Application API","contact":{"name":"Simpl Programme","url":"https://simpl-programme.ec.europa.eu/","email":"cnect-simpl@ec.europa.eu"},"license":{"name":"European Union Public License (EUPL) 1.2","url":"https://interoperable-europe.ec.europa.eu/sites/default/files/custom-page/attachment/eupl_v1.2_en.pdf"},"version":"1.0"},"servers":[{"url":"https://edc-connector-adapter.dev.simpl-europe.eu"}],"paths":{"/api/transfer/v1/start":{"post":{"tags":["Transfer Process"],"summary":"Initiate a new transfer process","operationId":"startTransfer","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferRequest"}}},"required":true},"responses":{"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"errorResponse":{"description":"errorResponse","value":{"status":503,"title":"Service Unavailable","detail":"EDC connector service is not available","timestamp":"2025-05-26T14:32:11.123Z"}}}}}},"400":{"description":"Invalid request","content":{}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Unauthorized Error Example":{"description":"Unauthorized Error Example","value":{"response":{"errorDescription":"Missing or invalid Authorization header"}}}}}}},"500":{"description":"Internal server error","content":{}},"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferProcessId"},"examples":{"transferProcessId":{"description":"transferProcessId","value":{"id":"transfer-process-567890"}}}}}}}}},"/api/registration/v1/register":{"post":{"tags":["Registation Controller"],"summary":"Register a SD json and return it enriched by EDC registration data","description":"Registers asset and policies into the provider EDC, creates a contract definition on EDC connector","operationId":"register","parameters":[{"name":"ecosystem","in":"query","description":"Ecosystem name","required":true,"schema":{"type":"string","description":"Ecosystem name","example":"simpl"},"example":"simpl"},{"name":"shapeFileName","in":"query","description":"File name","required":true,"schema":{"type":"string","description":"File name","example":"data-offeringShape.ttl"},"example":"data-offeringShape.ttl"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"jsonFile":{"type":"string","description":"SD json file","format":"binary"}}}}}},"responses":{"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"errorResponse":{"description":"errorResponse","value":{"status":503,"title":"Service Unavailable","detail":"EDC connector service is not available","timestamp":"2025-05-26T14:32:11.123Z"}}}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Bad Request Error Example":{"description":"Bad Request Error Example","value":{"keyErrorMessage":"VALIDATION_ERROR","response":{"errorTitle":"Missing argument","errorDescription":"Missing or invalid argument"}}}}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Unauthorized Error Example":{"description":"Unauthorized Error Example","value":{"response":{"errorDescription":"Missing or invalid Authorization header"}}}}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Internal Server Error Example":{"description":"Internal Server Error Example","value":{"keyErrorMessage":"GENERAL_ERROR","response":{"errorTitle":"Unexpected internal error","errorDescription":"Please try again"}}}}}}},"200":{"description":"Successfully registered SD json","content":{"application/json":{"schema":{"type":"string"},"examples":{"SD json file registered":{"description":"SD json file registered","value":{"@context":{"dcat":"http://www.w3.org/ns/dcat#","edc":"https://w3id.org/edc/v0.0.1/ns/"},"@id":"urn:uuid:9b220110-4482-2113-a786-6ace13b000ee","@type":"dcat:Dataset","edc:assetId":"asset-9876","edc:contractDefinitionId":"contract-def-45678","edc:providerId":"simpl-edc-provider-01"}}}}}}}}},"/api/contract/v1/offers/search":{"post":{"tags":["Contract Negotiation"],"summary":"Search for offers registered in the EDC provider catalog","operationId":"searchCatalogOffers","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractNegotiationRequest"}}},"required":true},"responses":{"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"errorResponse":{"description":"errorResponse","value":{"status":503,"title":"Service Unavailable","detail":"EDC connector service is not available","timestamp":"2025-05-26T14:32:11.123Z"}}}}}},"400":{"description":"Invalid request","content":{}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Unauthorized Error Example":{"description":"Unauthorized Error Example","value":{"response":{"errorDescription":"Missing or invalid Authorization header"}}}}}}},"500":{"description":"Internal server error","content":{}},"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogSearchResult"},"examples":{"catalogSearchResult":{"description":"catalogSearchResult","value":{"offers":[{"id":"offer-12345","assetId":"asset-9876","contractPolicy":{"policy":"example-policy"},"offerId":"edc://offer-12345"}],"providerId":"provider-edc-123"}}}}}}}}},"/api/contract/v1/negotiation/start":{"post":{"tags":["Contract Negotiation"],"summary":"Initiate a new contract negotiation","operationId":"startContractNegotiation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractNegotiationRequest"}}},"required":true},"responses":{"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"errorResponse":{"description":"errorResponse","value":{"status":503,"title":"Service Unavailable","detail":"EDC connector service is not available","timestamp":"2025-05-26T14:32:11.123Z"}}}}}},"400":{"description":"Invalid request","content":{}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Unauthorized Error Example":{"description":"Unauthorized Error Example","value":{"response":{"errorDescription":"Missing or invalid Authorization header"}}}}}}},"500":{"description":"Internal server error","content":{}},"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractNegotiationId"},"examples":{"contractNegotiationId":{"description":"contractNegotiationId","value":{"id":"negotiation-abcd1234"}}}}}}}}},"/api/transfer/v1/status/{id}":{"get":{"tags":["Transfer Process"],"summary":"Get the transfer process status","operationId":"getTransferStatus","parameters":[{"name":"id","in":"path","description":"The ID of the transfer process to retrieve the status for","required":true,"schema":{"type":"string"}}],"responses":{"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"errorResponse":{"description":"errorResponse","value":{"status":503,"title":"Service Unavailable","detail":"EDC connector service is not available","timestamp":"2025-05-26T14:32:11.123Z"}}}}}},"400":{"description":"Invalid request","content":{}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Unauthorized Error Example":{"description":"Unauthorized Error Example","value":{"response":{"errorDescription":"Missing or invalid Authorization header"}}}}}}},"500":{"description":"Internal server error","content":{}},"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferProcess"},"examples":{"transferProcess":{"description":"transferProcess","value":{"id":"transfer-process-567890","state":"COMPLETED","type":"CONSUMER","errorDetail":null,"dataDestination":{"type":"HttpData","baseUrl":"https://consumer-destination.example.com"},"contractAgreementId":"agreement-xyz789"}}}}}}}}},"/api/contract/v1/negotiation/status/{id}":{"get":{"tags":["Contract Negotiation"],"summary":"Get the contract negotiation status","operationId":"getContractNegotiationStatus","parameters":[{"name":"id","in":"path","description":"The ID of the contract negotiation to retrieve the status for","required":true,"schema":{"type":"string"}}],"responses":{"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"errorResponse":{"description":"errorResponse","value":{"status":503,"title":"Service Unavailable","detail":"EDC connector service is not available","timestamp":"2025-05-26T14:32:11.123Z"}}}}}},"400":{"description":"Invalid request","content":{}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Unauthorized Error Example":{"description":"Unauthorized Error Example","value":{"response":{"errorDescription":"Missing or invalid Authorization header"}}}}}}},"500":{"description":"Internal server error","content":{}},"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContractNegotiation"},"examples":{"contractNegotiation":{"description":"contractNegotiation","value":{"id":"negotiation-abcd1234","state":"CONFIRMED","counterPartyId":"provider-edc-123","counterPartyAddress":"https://provider-edc.example.com","contractAgreementId":"agreement-xyz789","errorDetail":null}}}}}}}}},"/api/configuration/v1/participant":{"get":{"tags":["Configuration Controller"],"summary":"Return the configured EDC participant id","description":"Return the configured EDC participant id to be used as Assigner in ODRL policies","operationId":"getParticipant","responses":{"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"errorResponse":{"description":"errorResponse","value":{"status":503,"title":"Service Unavailable","detail":"EDC connector service is not available","timestamp":"2025-05-26T14:32:11.123Z"}}}}}},"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Bad Request Error Example":{"description":"Bad Request Error Example","value":{"keyErrorMessage":"VALIDATION_ERROR","response":{"errorTitle":"Missing argument","errorDescription":"Missing or invalid argument"}}}}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Unauthorized Error Example":{"description":"Unauthorized Error Example","value":{"response":{"errorDescription":"Missing or invalid Authorization header"}}}}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"Internal Server Error Example":{"description":"Internal Server Error Example","value":{"keyErrorMessage":"GENERAL_ERROR","response":{"errorTitle":"Unexpected internal error","errorDescription":"Please try again"}}}}}}},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Participant"},"examples":{"participant":{"description":"participant","value":{"id":"simpl-edc-provider-01","did":"did:web:simpl-edc-provider-01"}}}}}}}}}},"components":{"schemas":{"ErrorResponse":{"type":"object","properties":{"errorTitle":{"type":"string"},"errorDescription":{"type":"string"}}},"DataDestination":{"type":"object","properties":{"type":{"type":"string"},"region":{"type":"string"},"consumerEmail":{"type":"string"},"storage":{"type":"string"},"bucketName":{"type":"string"},"objectName":{"type":"string"},"path":{"type":"string"},"accessKey":{"type":"string"},"secretKey":{"type":"string"},"keyName":{"type":"string"}}},"TransferRequest":{"type":"object","properties":{"providerEndpoint":{"type":"string"},"contractId":{"type":"string"},"dataDestination":{"$ref":"#/components/schemas/DataDestination"}}},"TransferProcessId":{"type":"object","properties":{"transferProcessId":{"type":"string"}}},"ContractNegotiationRequest":{"type":"object","properties":{"providerEndpoint":{"type":"string"},"contractDefinitionId":{"type":"string"},"assetId":{"type":"string"}}},"CatalogSearchResult":{"type":"object","properties":{"offers":{"type":"array","items":{"$ref":"#/components/schemas/Dataset"}}}},"Dataset":{"type":"object","properties":{"providerParticipantId":{"type":"string"},"providerEndpointUrl":{"type":"string"},"assetId":{"type":"string"},"offerId":{"type":"string"},"policy":{"$ref":"#/components/schemas/Policy"}}},"Policy":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["ACCESS","USAGE"]},"policyConstraints":{"type":"array","items":{"$ref":"#/components/schemas/PolicyConstraint"}}}},"PolicyConstraint":{"type":"object","properties":{"condition":{"type":"string"},"conditionOperator":{"type":"string"},"conditionValue":{"type":"string"}}},"ContractNegotiationId":{"type":"object","properties":{"contractNegotiationId":{"type":"string"}}},"TransferProcess":{"type":"object","properties":{"finalState":{"type":"string"},"assetId":{"type":"string"},"contractId":{"type":"string"},"correlationId":{"type":"string"},"transferType":{"type":"string"},"errorDetail":{"type":"string"},"type":{"type":"string"},"stateTimestamp":{"type":"integer","format":"int64"},"@id":{"type":"string"},"state":{"type":"string"}}},"ContractNegotiation":{"type":"object","properties":{"contractAgreementId":{"type":"string"},"state":{"type":"string"},"counterPartyAddress":{"type":"string"},"counterPartyId":{"type":"string"},"errorDetail":{"type":"string"},"protocol":{"type":"string"},"type":{"type":"string"},"createdAt":{"type":"integer","format":"int64"},"@id":{"type":"string"}}},"Participant":{"type":"object","properties":{"id":{"type":"string"}}}}}}
\ No newline at end of file
{"openapi":"3.0.1","info":{"title":"EDC Connector Adapter Application API","description":"OpenApi documentation for the EDC Connector Adapter Application API","version":"1.0"},"servers":[{"url":"https://edc-connector-adapter.dev.simpl-europe.eu"}],"paths":{"/api/v1/registration/register":{"post":{"tags":["Registation Controller"],"summary":"Register a SD json and return it enriched by EDC registration data","description":"Registers asset and policies into the provider EDC, creates a contract definition on EDC connector","operationId":"register","parameters":[{"name":"ecosystem","in":"query","description":"Ecosystem name","required":true,"schema":{"type":"string","description":"Ecosystem name","example":"simpl"},"example":"simpl"},{"name":"shapeFileName","in":"query","description":"File name","required":true,"schema":{"type":"string","description":"File name","example":"data-offeringShape.ttl"},"example":"data-offeringShape.ttl"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"jsonFile":{"type":"string","description":"SD json file","format":"binary"}}}}}},"responses":{"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"Successfully registered SD json","content":{"application/json":{"schema":{"type":"object"},"examples":{"SD json file registered":{"description":"SD json file registered","value":"SD json file registered"}}}}}}}},"/api/v1/configuration/participant":{"get":{"tags":["Configuration Controller"],"summary":"Return the configured EDC participant id","description":"Return the configured EDC participant id to be used as Assigner in ODRL policies","operationId":"getParticipant","responses":{"400":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"OK","content":{"application/json":{"schema":{"type":"string"}}}}}}}},"components":{"schemas":{"ErrorResponse":{"type":"object","properties":{"errorTitle":{"type":"string"},"errorDescription":{"type":"string"}}}}}}
\ No newline at end of file
PROJECT_VERSION_NUMBER="1.1.2"
\ No newline at end of file
PROJECT_VERSION_NUMBER="1.2.0"
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
......@@ -8,7 +7,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<version>3.4.5</version>
</parent>
<groupId>eu.europa.ec.simpl.sdtooling</groupId>
......@@ -22,15 +21,16 @@
<java.version>21</java.version>
<!-- artifacts -->
<data1.common.version>0.3.0</data1.common.version>
<opentelemetry.instrumentation.version>2.15.0</opentelemetry.instrumentation.version>
<data1-common.version>0.3.0</data1-common.version>
<opentelemetry-instrumentation.version>2.15.0</opentelemetry-instrumentation.version>
<simpl-data1-common.version>1.0.0</simpl-data1-common.version>
<simpl-data1-common-adapter.version>1.0.0</simpl-data1-common-adapter.version>
<!-- plugins -->
<belgif.rest.guide.validator.maven.plugin.version>2.1.0</belgif.rest.guide.validator.maven.plugin.version>
<jacoco.maven.plugin>0.8.12</jacoco.maven.plugin>
<license.maven.plugin.version>2.5.0</license.maven.plugin.version>
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
<springdoc.openapi.maven.plugin.version>1.1</springdoc.openapi.maven.plugin.version>
<belgif-rest-guide-validator-maven-plugin.version>2.1.0</belgif-rest-guide-validator-maven-plugin.version>
<jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version>
<license-maven-plugin.version>2.5.0</license-maven-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
</properties>
......@@ -45,6 +45,10 @@
<id>gitlab-simpl-data1-common</id>
<url>https://code.europa.eu/api/v4/projects/1092/packages/maven</url>
</repository>
<repository>
<id>gitlab-simpl-data1-common-adapter</id>
<url>https://code.europa.eu/api/v4/projects/1120/packages/maven</url>
</repository>
</repositories>
......@@ -53,7 +57,7 @@
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>${opentelemetry.instrumentation.version}</version>
<version>${opentelemetry-instrumentation.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
......@@ -61,10 +65,16 @@
</dependencyManagement>
<dependencies>
<dependency>
<groupId>eu.europa.ec.simpl</groupId>
<artifactId>simpl-data1-common</artifactId>
<version>${simpl-data1-common.version}</version>
</dependency>
<dependency>
<groupId>eu.europa.ec.simpl</groupId>
<artifactId>data1-common</artifactId>
<version>${data1.common.version}</version>
<artifactId>simpl-data1-common-adapter</artifactId>
<version>${simpl-data1-common-adapter.version}</version>
</dependency>
<dependency>
......@@ -113,20 +123,29 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
<!-- disable logback logging -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
</dependencies>
<build>
......@@ -141,12 +160,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.plugin.version}</version><!--$NO-MVN-MAN-VER$-->
<version>${maven-surefire-plugin.version}</version><!--$NO-MVN-MAN-VER$-->
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>${maven.surefire.plugin.version}</version>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
......@@ -178,7 +197,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.maven.plugin}</version>
<version>${jacoco-maven-plugin.version}</version>
<configuration>
<excludes>
<exclude>**/*Application.class</exclude>
......@@ -200,25 +219,6 @@
</executions>
</plugin>
<!-- OpenAPI file generation -->
<!--
1) launch the application
2) mvn springdoc-openapi:generate
-->
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>${springdoc.openapi.maven.plugin.version}</version>
<configuration>
<outputDir>${project.basedir}</outputDir>
<!-- YAML
<apiDocsUrl>http://localhost:8080/api-docs.yaml</apiDocsUrl>
<outputFileName>openapi.yaml</outputFileName>
-->
<apiDocsUrl>http://localhost:8080/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
</configuration>
</plugin>
<!-- OpenAPI file validation -->
<!--
mvn belgif-rest-guide-validator:validate
......@@ -226,7 +226,7 @@
<plugin>
<groupId>io.github.belgif.rest.guide.validator</groupId>
<artifactId>belgif-rest-guide-validator-maven-plugin</artifactId>
<version>${belgif.rest.guide.validator.maven.plugin.version}</version>
<version>${belgif-rest-guide-validator-maven-plugin.version}</version>
<configuration>
<skipOnErrors>true</skipOnErrors>
<outputTypes>
......@@ -240,13 +240,11 @@
</configuration>
</plugin>
<!--Spotless & Palantir Configuration-->
<!-- Spotless & Palantir code formatting -->
<!--
1) mvn spotless:check (optional) to see the report
2) mvn spotless:apply
-->
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
......@@ -268,7 +266,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>${license.maven.plugin.version}</version>
<version>${license-maven-plugin.version}</version>
<executions>
<execution>
<id>download-licenses</id>
......
package eu.europa.ec.simpl.edcconnectoradapter.client.edcconnector;
import com.fasterxml.jackson.databind.JsonNode;
import java.net.URI;
import java.util.Map;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(value = "edcConnectorCatalogClient", url = "placeholder")
public interface EDCConnectorCatalogClient {
@PostMapping("/v3/catalog/request")
JsonNode requestCatalog(URI hostname, @RequestHeader Map<String, String> header, @RequestBody Object requestBody);
}
package eu.europa.ec.simpl.edcconnectoradapter.client.edcconnector;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.common.response.EdcAcknowledgementId;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.contract.response.EdcContractNegotiation;
import java.net.URI;
import java.util.Map;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(value = "edcConnectorContractClient", url = "placeholder")
public interface EDCConnectorContractClient {
@PostMapping("/v3/contractnegotiations")
EdcAcknowledgementId initiateContractNegotiation(
URI hostname, @RequestHeader Map<String, String> header, @RequestBody Object requestBody);
@GetMapping("/v3/contractnegotiations/{id}")
EdcContractNegotiation getContractNegotiation(
URI hostname, @RequestHeader Map<String, String> header, @PathVariable String id);
}
package eu.europa.ec.simpl.edcconnectoradapter.client.edcconnector;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.management.AssetDefinition;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.management.ContractDefinition;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.management.PolicyDefinition;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.management.request.EdcAssetDefinition;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.management.request.EdcContractDefinition;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.management.request.EdcPolicyDefinition;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(value = "edcConnectorClient", url = "${edc-connector.base-url}")
public interface EDCConnectorClient {
public interface EDCConnectorManagementClient {
String EDC_MANAGEMENT_PATH = "/management/v3";
......@@ -17,7 +17,7 @@ public interface EDCConnectorClient {
ResponseEntity<String> registerAsset(@RequestBody String body);
@PostMapping(value = EDC_MANAGEMENT_PATH + "/assets", consumes = "application/json", produces = "application/json")
ResponseEntity<String> register(@RequestBody AssetDefinition asset);
ResponseEntity<String> register(@RequestBody EdcAssetDefinition asset);
@PostMapping(
value = EDC_MANAGEMENT_PATH + "/policydefinitions",
......@@ -29,7 +29,7 @@ public interface EDCConnectorClient {
value = EDC_MANAGEMENT_PATH + "/policydefinitions",
consumes = "application/json",
produces = "application/json")
ResponseEntity<String> register(@RequestBody PolicyDefinition policyDefinition);
ResponseEntity<String> register(@RequestBody EdcPolicyDefinition policyDefinition);
@PostMapping(
value = EDC_MANAGEMENT_PATH + "/contractdefinitions",
......@@ -41,5 +41,5 @@ public interface EDCConnectorClient {
value = EDC_MANAGEMENT_PATH + "/contractdefinitions",
consumes = "application/json",
produces = "application/json")
ResponseEntity<String> register(@RequestBody ContractDefinition contractDefinition);
ResponseEntity<String> register(@RequestBody EdcContractDefinition contractDefinition);
}
package eu.europa.ec.simpl.edcconnectoradapter.client.edcconnector;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.common.response.EdcAcknowledgementId;
import eu.europa.ec.simpl.edcconnectoradapter.model.edc.transfer.response.EdcTransferProcess;
import java.net.URI;
import java.util.Map;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(value = "edcConnectorTransferClient", url = "placeholder")
public interface EDCConnectorTransferClient {
@PostMapping("/v3/transferprocesses")
EdcAcknowledgementId startTransferProcess(
URI hostname, @RequestHeader Map<String, String> header, @RequestBody Object requestBody);
@GetMapping("/v3/transferprocesses/{id}")
EdcTransferProcess getTransferProcess(
URI hostname, @RequestHeader Map<String, String> header, @PathVariable String id);
@PostMapping("/v3/transferprocesses/{id}/deprovision")
Object deprovisioningTransferProcess(
URI hostname, @RequestHeader Map<String, String> header, @PathVariable String id);
}
......@@ -13,6 +13,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
basePackages = {
"eu.europa.ec.simpl.edcconnectoradapter",
"eu.europa.ec.simpl.data1.common.config.feign",
"eu.europa.ec.simpl.data1.common.config.openapi",
"eu.europa.ec.simpl.data1.common.config.webmvc",
"eu.europa.ec.simpl.data1.common.controller.status"
})
......
package eu.europa.ec.simpl.edcconnectoradapter.config;
import eu.europa.ec.simpl.edcconnectoradapter.exception.DeprovisioningtException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.log4j.Log4j2;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.security.plain.PlainLoginModule;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.kafka.KafkaException;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaAdmin;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.listener.ContainerProperties;
import org.springframework.kafka.listener.DefaultErrorHandler;
import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer;
import org.springframework.util.backoff.ExponentialBackOff;
@Profile("consumer")
@Configuration
@EnableKafka
@Log4j2
public class KafkaConfig {
public static final String SASL_MECHANISM_PLAIN = "PLAIN";
private static final String SASL_JAAS_CONFIG_FORMAT = "%s required username=\"%s\" password=\"%s\";";
private static final int ADMIN_OPERATION_TIMEOUT_SECS = 5;
private static final int ADMIN_CLOSE_TIMEOUT_SECS = 5;
public enum AuthType {
SASL_PLAINTEXT
}
@Value(value = "${spring.kafka.bootstrap-servers}")
private String bootstrapAddress;
@Value(value = "${spring.kafka.consumer.group-id}")
private String groupId;
@Value(value = "${kafka.backoff.interval}")
private Long backoffInterval;
@Value(value = "${kafka.backoff.multiplier}")
private Long backoffMultiplier;
@Value(value = "${kafka.backoff.max_failure}")
private Integer backoffMaxAttempts;
@Value(value = "${kafka.auth.type}")
private AuthType authType;
@Value(value = "${kafka.auth.sasl.username}")
private String username;
@Value(value = "${kafka.auth.sasl.password}")
private String password;
@Value(value = "${kafka.transfer-process.topic}")
private String transferProcessTopic;
@Value(value = "${kafka.fatal-if-broker-not-available}")
private boolean fatalIfBrokerNotAvailable;
@Bean
KafkaAdmin kafkaAdmin() {
Map<String, Object> config = new HashMap<>();
setupCommons(config);
config.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, "3000");
KafkaAdmin kafkaAdmin = new KafkaAdmin(config);
// this will work only if a NewTopic bean is defined (see newTopic())
kafkaAdmin.setFatalIfBrokerNotAvailable(fatalIfBrokerNotAvailable);
// reduce the time for admin operations
kafkaAdmin.setOperationTimeout(ADMIN_OPERATION_TIMEOUT_SECS);
kafkaAdmin.setCloseTimeout(ADMIN_CLOSE_TIMEOUT_SECS);
return kafkaAdmin;
}
/**
* This is the only way to enable the kafka connection verification during the application startup (see kafkaAdmin.fatalIfBrokerNotAvailable)
* @return
*/
@Bean
NewTopic newTopic() {
return new NewTopic(transferProcessTopic, 1, (short) 1);
}
@Bean
ProducerFactory<String, Object> producerFactory() {
Map<String, Object> config = new HashMap<>();
setupCommons(config);
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(config);
}
@Bean
ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> props = new HashMap<>();
setupCommons(props);
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, ErrorHandlingDeserializer.class);
props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, ErrorHandlingDeserializer.class);
return new DefaultKafkaConsumerFactory<>(props);
}
@Bean
ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
@Value("${kafka.consumerThreads:1}") int consumerThreads) {
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setConcurrency(consumerThreads);
factory.setCommonErrorHandler(errorHandler());
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.RECORD);
return factory;
}
@Bean
KafkaTemplate<String, Object> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
@Bean
DefaultErrorHandler errorHandler() {
ExponentialBackOff exponentialBackOff = new ExponentialBackOff(backoffInterval, backoffMultiplier);
exponentialBackOff.setMaxAttempts(backoffMaxAttempts);
DefaultErrorHandler errorHandler = new DefaultErrorHandler(
(consumerRecord, exception) ->
// logic to execute when all the retry attempts are exhausted
log.error("Retries exceed when processing record: {}", consumerRecord.value()),
exponentialBackOff);
errorHandler.addRetryableExceptions(DeprovisioningtException.class);
errorHandler.addNotRetryableExceptions(IOException.class);
errorHandler.addNotRetryableExceptions(KafkaException.class);
return errorHandler;
}
private void setupCommons(Map<String, Object> config) {
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress);
if (authType == AuthType.SASL_PLAINTEXT) {
setupSASL(config);
}
}
private void setupSASL(Map<String, Object> config) {
log.info("setupSASL() with PLAIN mechanism");
config.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT");
config.put(SaslConfigs.SASL_MECHANISM, SASL_MECHANISM_PLAIN);
config.put(
SaslConfigs.SASL_JAAS_CONFIG,
String.format(SASL_JAAS_CONFIG_FORMAT, PlainLoginModule.class.getName(), username, password));
}
}
package eu.europa.ec.simpl.edcconnectoradapter.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.servers.Server;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
private static final String TITLE = "EDC Connector Adapter Application API";
private static final String VERSION = "1.0";
private static final String DESCRIPTION = "OpenApi documentation for the " + TITLE;
@Value("${openapi-config.servers}")
private List<String> servers;
@Bean
OpenAPI openAPI() {
Info info = new Info().title(TITLE).description(DESCRIPTION).version(VERSION);
List<Server> serversList =
servers.stream().map(url -> new Server().url(url)).toList();
return new OpenAPI().info(info).servers(serversList);
}
}
......@@ -2,7 +2,14 @@ package eu.europa.ec.simpl.edcconnectoradapter.constant;
public final class Constants {
public static final String EDC_CONNECTOR_ERROR_TYPE = "EDC_CONNECTOR_ERROR";
public static final String EDC_PREFIX = "https://w3id.org/edc/v0.0.1/ns/";
public static final String EDC_ASSET_ID_PROPERTY = "id";
public static final String EDC_EQUAL = "=";
public static final String EDC_MANAGEMENT_PATH = "/management";
public static final String EDC_PROTOCOL_PATH = "/protocol";
public static final String EDC_API_KEY_HEADER = "x-api-key";
public static final int DEFAULT_EDC_QUERY_LIMIT = 9999;
private Constants() {}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment