Skip to content

Metaform/ccm-migration

Repository files navigation

CCM Migration Adapter

A sample Certificate Consumer that bridges the new CloudEvents-based Company Certificate Management API (CX-0135) to a legacy Apache Kafka backend built against the previous CCM message format (v2.4.0). It demonstrates how an existing Kafka-based consumer backend can be adapted to the new API without being rewritten — the adapter is an anti-corruption layer that translates between the two formats.

It follows the conventions of the reference implementation Metaform/certo (Java 25, Spring Boot 4, Gradle Kotlin DSL, Jackson 3, OkHttp). It implements only the consumer side; provider-side functions (issuing, fulfilling, hosting certificates) are out of scope.

📖 Migration guide — the endpoint-, status-, and field-level mapping from the old CCM format (v2.4.0) to the new CloudEvents API. It uses this adapter (the anti-corruption layer) as its explanatory device: every translation the guide describes is one this app performs.

What it does

                     NEW CCM (CloudEvents / HTTP)                 OLD CCM (JSON over Kafka)
                                                          
  Certificate         ┌────────────────────────────┐         ┌──────────────────────────┐
   Provider  ─────────►  POST /certificate-        │         │                          │
  (e.g. certo)  (1)   │       notifications        │  (3)    │  topic:                  │
                      │            │ CREATED       ├────────►  ccm.certificates.inbound ──► Legacy
       ▲              │            ▼               │  push   │                          │    backend
       │              │  (2) GET /certificates/{id}│         │                          │   (unchanged)
       │  (6) POST    │   (metadata + docs pull)   │         │                          │
       │  /certificate-acceptance-notifications    │         │  topic:                  │
       └──────────────┤            ▲               │  (5)    │  ccm.feedback.outbound   │◄── feedback
                      │            └───────────────┼─────────┤                          │
                      │   (4) translate feedback   │ consume │                          │
                      └────────────────────────────┘         └──────────────────────────┘
                              CCM Migration Adapter

Inbound — receive a certificate (new → old):

  1. The provider sends a CertificateLifecycleStatus CloudEvent to POST /certificate-notifications.
  2. On CREATED (or MODIFIED), the adapter retrieves the certificate metadata (GET /certificates/{id}, JSON with a documents[] array) and then each document binary (GET /documents/{id}).
  3. It translates the certificate into old-CCM push messages (each document inline as base64) and publishes them to the ccm.certificates.inbound Kafka topic the legacy backend already consumes. The old format carries one document, so the adapter emits one push per document.

Outbound — provide feedback (old → new): 4. The legacy backend validates the certificate and publishes an old-CCM status (feedback) message to ccm.feedback.outbound. 5. The adapter consumes it, translates it to a new CertificateAcceptanceStatus outcome, and … 6. … reports it to the provider via POST /certificate-acceptance-notifications.

The provider can also query the adapter's recorded decision via GET /certificate-acceptance-status/{exchangeId}.

Translation reference

Status mapping

Old (feedback certificateStatus) New (AcceptanceStatus)
RECEIVED RETRIEVED
ACCEPTED ACCEPTED
REJECTED REJECTED

Old certificateErrors[] + nested locationErrors[] are flattened into one new errors[]; a per-location error carries its BPN as the specifier.

Identity mapping

Old New
documentId (feedback) certificateId
(correlation) exchangeId (from a CREATED event)

Field mapping (new → old push). The combined retrieval metadata is rich — issuer, validator, registrationNumber, trustLevel, and structured enclosedSites are all returned — so the adapter maps them straight into the old push. Only uploader has no source (removed in the new protocol) and is emitted as null.

Caveats.

  • A certificate may have multiple documents; the old push carries one, so the adapter emits one push per document (per the migration guide; primary-only or a list are alternatives).
  • WITHDRAWN lifecycle events have no old-CCM equivalent and are logged only (not bridged).
  • CertificateFulfillmentStatus events are acknowledged but ignored — this adapter is push-only and opens no consumer-initiated requests.
  • Feedback can only be reported for a certificate the adapter saw a CREATED event for (only CREATED opens an exchange, per CX-0135 §2.2.4).

Running

Prerequisites: Docker (for Kafka). The Gradle build provisions JDK 25 automatically.

  1. Start Kafka:
    docker compose up -d
  2. (Optional) Run a certo provider on http://localhost:8080 so retrieval and acceptance callbacks have a real counterpart. Otherwise the inbound flow logs a retrieval failure and the outbound flow logs a failed callback — the translation still runs.
  3. Run the adapter (on port 8081):
    ./gradlew bootRun

Try the inbound flow

Send a CREATED lifecycle event (the adapter will pull the certificate and publish a legacy push):

curl -i -X POST http://localhost:8081/certificate-notifications \
  -H 'Content-Type: application/cloudevents+json' \
  -d '{
    "specversion": "1.0",
    "type": "org.catena-x.ccm.CertificateLifecycleStatus.v1",
    "source": "urn:bpn:BPNL0000000001AB",
    "subject": "BPNL0000000002CD",
    "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "time": "2025-05-04T07:00:00Z",
    "sourcebpn": "BPNL0000000001AB",
    "data": {
      "exchangeId": "exch-001",
      "certificateId": "cert-001",
      "version": 1,
      "status": "CREATED",
      "datasetId": "dataset-001",
      "certificateType": "ISO9001",
      "validFrom": "2023-01-25",
      "validUntil": "2026-01-24",
      "locationBpns": ["BPNS00000003AYRE"]
    }
  }'

Observe the translated old-CCM push on Kafka:

docker exec ccm-migration-kafka /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 --topic ccm.certificates.inbound --from-beginning

Try the outbound flow

Publish a legacy feedback message (as the legacy backend would). documentId is the certificateId from the inbound step:

docker exec -i ccm-migration-kafka /opt/kafka/bin/kafka-console-producer.sh \
  --bootstrap-server localhost:9092 --topic ccm.feedback.outbound <<'EOF'
{"header":{"context":"CompanyCertificateManagement-CCMAPI-Status:1.0.0","version":"3.1.0"},"content":{"documentId":"cert-001","certificateStatus":"ACCEPTED","locationBpns":["BPNS00000003AYRE"]}}
EOF

The adapter reports a CertificateAcceptanceStatus CloudEvent to the provider and records the outcome:

curl -s http://localhost:8081/certificate-acceptance-status/exch-001 | jq

Configuration

src/main/resources/application.yaml (ccm.*):

Key Default Meaning
ccm.provider-base-url http://localhost:8080 Provider data plane (retrieve + acceptance)
ccm.consumer.bpn/.source BPNL0000000002CD This adapter's consumer identity
ccm.provider.bpn/.source BPNL0000000001AB The provider's identity
ccm.kafka.certificates-topic ccm.certificates.inbound Topic the adapter publishes legacy push to
ccm.kafka.feedback-topic ccm.feedback.outbound Topic the adapter consumes legacy feedback from
spring.kafka.bootstrap-servers localhost:9092 Kafka broker

Layout

common/cloudevent  CloudEvents envelope, codec, dedup (mirrors certo)
common/model       Lifecycle / Acceptance status models
common/web         Error handling
config             MigrationProperties
client             Provider HTTP clients (metadata retrieve, document retrieve, acceptance callback)
legacy             Old-CCM v2.4.0 message records + LegacyTranslator (the bridge core)
consumer           Notification API + CertificateConsumerService + CorrelationStore
bridge             Kafka producer (push out) + feedback listener (feedback in)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages