From 71557131c67fb4c133b103e5a663849c0a661bd6 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Thu, 29 Jan 2026 14:55:38 +0100 Subject: [PATCH 01/12] feat: file upload cel and permissions body part --- .../api/controller/EdcDataController.java | 28 ++++++++++----- .../domain/service/DataAccessService.java | 34 ++++++++++++------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index a0c751b..fffc285 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -18,21 +18,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.metaformsystems.redline.api.dto.request.ContractRequest; import com.metaformsystems.redline.api.dto.request.CounterPartyIdWrapper; +import com.metaformsystems.redline.api.dto.request.FileUploadRequest; import com.metaformsystems.redline.api.dto.request.TransferProcessRequest; import com.metaformsystems.redline.api.dto.response.Contract; import com.metaformsystems.redline.api.dto.response.ContractNegotiation; import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.domain.service.DataAccessService; -import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; -import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; -import com.metaformsystems.redline.infrastructure.client.management.dto.Obligation; -import com.metaformsystems.redline.infrastructure.client.management.dto.Offer; -import com.metaformsystems.redline.infrastructure.client.management.dto.Permission; -import com.metaformsystems.redline.infrastructure.client.management.dto.Prohibition; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; +import com.metaformsystems.redline.infrastructure.client.management.dto.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Encoding; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -51,6 +47,7 @@ import java.io.IOException; import java.time.Instant; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -84,12 +81,27 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @PathVariable Long providerId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, + @RequestPart(value = "celExpressions", required = false) String celExpressions, + @RequestPart(value = "permissions", required = false) PolicySet permissions, @RequestPart("file") MultipartFile file) { try { var publicMetadataMap = objectMapper.readValue(publicMetadata, new TypeReference>() {}); var privateMetadataMap = objectMapper.readValue(privateMetadata, new TypeReference>() {}); - dataAccessService.uploadFileForParticipant(participantId, publicMetadataMap, privateMetadataMap, file.getInputStream(), file.getContentType(), file.getOriginalFilename()); + List celExpressionList = null; + if (celExpressions != null) { + celExpressionList = objectMapper.readValue(celExpressions, new TypeReference<>() {}); + } + dataAccessService.uploadFileForParticipant( + participantId, + publicMetadataMap, + privateMetadataMap, + file.getInputStream(), + file.getContentType(), + file.getOriginalFilename(), + celExpressionList, + permissions + ); } catch (IOException e) { return ResponseEntity.internalServerError().build(); } diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 59cafac..24502ab 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -21,13 +21,7 @@ import com.metaformsystems.redline.domain.repository.ParticipantRepository; import com.metaformsystems.redline.infrastructure.client.dataplane.DataPlaneApiClient; import com.metaformsystems.redline.infrastructure.client.management.ManagementApiClient; -import com.metaformsystems.redline.infrastructure.client.management.dto.Asset; -import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; -import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; -import com.metaformsystems.redline.infrastructure.client.management.dto.ContractNegotiation; -import com.metaformsystems.redline.infrastructure.client.management.dto.ContractRequest; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferRequest; +import com.metaformsystems.redline.infrastructure.client.management.dto.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -39,11 +33,7 @@ import java.io.InputStream; import java.time.Duration; import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -72,11 +62,23 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet permissions) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); + + //-1. create CEL expressions + if (celExpressions != null) { + celExpressions.forEach(celExpression -> { + try { + managementApiClient.createCelExpression(celExpression); + } catch (WebClientResponseException.Conflict e) { + //do nothing, CEL expression already exists + } + }); + } + //0. upload file to data plane var assetId = UUID.randomUUID().toString(); publicMetadata.put("assetId", assetId); @@ -106,6 +108,11 @@ public void uploadFileForParticipant(Long participantId, Map pub //2. create policy var policy = MEMBERSHIP_POLICY; + if (permissions != null) { + var permissionList = new ArrayList<>(policy.getPolicy().getPermission()); + permissionList.addAll(permissions.getPermission()); + policy.getPolicy().setPermission(permissionList); + } policy.setId(UUID.randomUUID().toString()); managementApiClient.createPolicy(participantContextId, policy); @@ -114,6 +121,7 @@ public void uploadFileForParticipant(Long participantId, Map pub contractDef.setId(UUID.randomUUID().toString()); contractDef.setContractPolicyId(policy.getId()); contractDef.setAccessPolicyId(policy.getId()); + contractDef.setAssetsSelector(Set.of(new Criterion("id", "=", assetId))); managementApiClient.createContractDefinition(participantContextId, contractDef); From 0ab4d48572253eece7e408bfb65cc752d244ca6a Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 12:06:19 +0100 Subject: [PATCH 02/12] fix: policy definition --- .../api/controller/EdcDataController.java | 20 ++++------- .../redline/domain/service/Constants.java | 26 +-------------- .../domain/service/DataAccessService.java | 33 +++++++++---------- 3 files changed, 23 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index fffc285..57cc251 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.metaformsystems.redline.api.dto.request.ContractRequest; import com.metaformsystems.redline.api.dto.request.CounterPartyIdWrapper; -import com.metaformsystems.redline.api.dto.request.FileUploadRequest; import com.metaformsystems.redline.api.dto.request.TransferProcessRequest; import com.metaformsystems.redline.api.dto.response.Contract; import com.metaformsystems.redline.api.dto.response.ContractNegotiation; @@ -28,26 +27,17 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Encoding; 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 io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -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; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.time.Instant; -import java.util.Collection; import java.util.List; import java.util.Map; @@ -82,7 +72,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, @RequestPart(value = "celExpressions", required = false) String celExpressions, - @RequestPart(value = "permissions", required = false) PolicySet permissions, + @RequestPart(value = "constraints", required = false) String constraints, @RequestPart("file") MultipartFile file) { try { @@ -92,6 +82,10 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, if (celExpressions != null) { celExpressionList = objectMapper.readValue(celExpressions, new TypeReference<>() {}); } + List constraintList = null; + if (constraints != null) { + constraintList = objectMapper.readValue(constraints, new TypeReference<>() {}); + } dataAccessService.uploadFileForParticipant( participantId, publicMetadataMap, @@ -100,7 +94,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, file.getContentType(), file.getOriginalFilename(), celExpressionList, - permissions + constraintList ); } catch (IOException e) { return ResponseEntity.internalServerError().build(); diff --git a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java index 2817b78..0d8187f 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java @@ -14,35 +14,11 @@ package com.metaformsystems.redline.domain.service; -import com.metaformsystems.redline.infrastructure.client.management.dto.Criterion; -import com.metaformsystems.redline.infrastructure.client.management.dto.NewContractDefinition; -import com.metaformsystems.redline.infrastructure.client.management.dto.NewPolicyDefinition; import com.metaformsystems.redline.infrastructure.client.management.dto.PolicySet; -import java.util.List; -import java.util.Set; - public interface Constants { String ASSET_PERMISSION = "membership_asset"; - String MEMBERSHIP_POLICY_ID = "membership_policy"; String MEMBERSHIP_EXPRESSION_ID = "membership_expr"; String MEMBERSHIP_EXPRESSION = "ctx.agent.claims.vc.filter(c, c.type.exists(t, t == 'MembershipCredential')).exists(c, c.credentialSubject.exists(cs, timestamp(cs.membershipStartDate) < now))"; - String CONTRACT_DEFINITION_ID = "membership_contract_definition"; - - // all files that are uploaded fall under this policy: the MembershipCredential must be presented to view the EDC asset - NewPolicyDefinition MEMBERSHIP_POLICY = NewPolicyDefinition.Builder.aNewPolicyDefinition() - .id(MEMBERSHIP_POLICY_ID) - .policy(new PolicySet(List.of(new PolicySet.Permission("use", - List.of(new PolicySet.Constraint("MembershipCredential", "eq", "active")))))) - .build(); - - // all new assets must have privateProperties: "permission" - "membership_asset", so that they are affected by this contract def - NewContractDefinition MEMBERSHIP_CONTRACT_DEFINITION = NewContractDefinition.Builder.aNewContractDefinition() - .id(CONTRACT_DEFINITION_ID) - .accessPolicyId(MEMBERSHIP_POLICY_ID) - .contractPolicyId(MEMBERSHIP_POLICY_ID) - .assetsSelector(Set.of(new Criterion("privateProperties.'https://w3id.org/edc/v0.0.1/ns/permission'", "=", ASSET_PERMISSION))) - .build(); - - + PolicySet.Constraint MEMBERSHIP_CONSTRAINT = new PolicySet.Constraint("MembershipCredential", "eq", "active"); } diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 24502ab..13fb2ef 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -38,11 +38,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.metaformsystems.redline.domain.service.Constants.ASSET_PERMISSION; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_CONTRACT_DEFINITION; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION_ID; -import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_POLICY; +import static com.metaformsystems.redline.domain.service.Constants.*; @Service public class DataAccessService { @@ -62,7 +58,7 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet permissions) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, List constraints) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); @@ -107,22 +103,23 @@ public void uploadFileForParticipant(Long participantId, Map pub } //2. create policy - var policy = MEMBERSHIP_POLICY; - if (permissions != null) { - var permissionList = new ArrayList<>(policy.getPolicy().getPermission()); - permissionList.addAll(permissions.getPermission()); - policy.getPolicy().setPermission(permissionList); + var policy = NewPolicyDefinition.Builder.aNewPolicyDefinition() + .id(UUID.randomUUID().toString()) + .policy(new PolicySet(List.of(new PolicySet.Permission("use", + new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)) + )))).build(); + if (constraints != null) { + policy.getPolicy().getPermission().getFirst().getConstraint().addAll(constraints); } - policy.setId(UUID.randomUUID().toString()); managementApiClient.createPolicy(participantContextId, policy); //3. create contract definition if none exists - var contractDef = MEMBERSHIP_CONTRACT_DEFINITION; - contractDef.setId(UUID.randomUUID().toString()); - contractDef.setContractPolicyId(policy.getId()); - contractDef.setAccessPolicyId(policy.getId()); - contractDef.setAssetsSelector(Set.of(new Criterion("id", "=", assetId))); - managementApiClient.createContractDefinition(participantContextId, contractDef); + var contractDef = NewContractDefinition.Builder.aNewContractDefinition(); + contractDef.id(UUID.randomUUID().toString()); + contractDef.contractPolicyId(policy.getId()); + contractDef.accessPolicyId(policy.getId()); + contractDef.assetsSelector(Set.of(new Criterion("id", "=", assetId))); + managementApiClient.createContractDefinition(participantContextId, contractDef.build()); //2. track uploaded file in DB From 014df3464a8aadfeba7ea1ba5eb36216742b21e5 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 14:27:33 +0100 Subject: [PATCH 03/12] e2e: include cel expression and constraint --- .../redline/TransferEndToEndTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java index 714a345..30a80bf 100644 --- a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java +++ b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java @@ -25,6 +25,7 @@ import com.metaformsystems.redline.api.dto.response.Participant; import com.metaformsystems.redline.api.dto.response.Tenant; import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; +import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; import io.restassured.http.ContentType; @@ -39,6 +40,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import static io.restassured.RestAssured.given; @@ -80,11 +82,21 @@ void testTransferFile() throws Exception { log.info("uploading file to provider"); // upload file for consumer - this creates asset, policy, contract-def, etc. + var celExpressions = List.of(CelExpression.Builder.aNewCelExpression() + .id("counter-party-id-" + slug) + .leftOperand("CounterPartyId") + .description("Counter Party Access Policy") + .expression("ctx.agent.id == this.rightOperand") + .scopes(Set.of("catalog", "contract.negotiation", "transfer.process")) + .build()); + var constraints = List.of(new Constraint("CounterPartyId", "eq", consumerDid)); baseRequest() .contentType(ContentType.MULTIPART) .multiPart("file", "testfile.txt", "This is a test file.".getBytes()) .multiPart("publicMetadata", "{\"slug\": \"%s\"}".formatted(slug), "application/json") .multiPart("privateMetadata", "{\"privateSlug\": \"%s\"}".formatted(slug), "application/json") + .multiPart("celExpressions", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(celExpressions), "application/json") + .multiPart("constraints", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(constraints), "application/json") .post("/api/ui/service-providers/%s/tenants/%s/participants/%s/files".formatted(SERVICE_PROVIDER_ID, provider.tenantId(), provider.participantId())) .then() .statusCode(200); From 9cd32af893b40ff448e7c63ae17172c73ab1dc6e Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 14:39:44 +0100 Subject: [PATCH 04/12] test: update and add tests --- .../api/controller/EdcDataControllerTest.java | 44 ++++++++------- .../DataAccessServiceIntegrationTest.java | 53 +++++++++++++++++++ 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java index 89bfdb8..b36b5c6 100644 --- a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java +++ b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java @@ -168,6 +168,13 @@ void shouldUploadFile() throws Exception { publicMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); var privateMetadata = new MockPart("privateMetadata", "{\"private\": \"value\"}".getBytes()); privateMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var celExpressions = new MockPart("celExpressions", "[{\"id\":\"custom-expression\",\"leftOperand\":\"CustomCredential\",\"description\":\"Custom expression\",\"expression\":\"true\",\"scopes\":[\"catalog\"]}]".getBytes()); + celExpressions.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var constraints = new MockPart("constraints", "[{\"leftOperand\":\"purpose\",\"operator\":\"eq\",\"rightOperand\":\"test\"}]".getBytes()); + constraints.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + // mock create-cel-expression (custom) + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); // Mock the upload response from the dataplane mockWebServer.enqueue(new MockResponse() @@ -175,10 +182,10 @@ void shouldUploadFile() throws Exception { .setBody("{\"id\": \"generated-file-id-123\"}") .addHeader("Content-Type", "application/json")); - // mock create-cel-expression + // mock create-asset mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - // mock create-asset + // mock create-cel-expression (membership) mockWebServer.enqueue(new MockResponse().setResponseCode(200)); // mock create-policy @@ -191,7 +198,7 @@ void shouldUploadFile() throws Exception { mockMvc.perform(multipart("/api/ui/service-providers/{providerId}/tenants/{tenantId}/participants/{participantId}/files", serviceProvider.getId(), tenant.getId(), participant.getId()) .file(mockFile) - .part(publicMetadata, privateMetadata)) + .part(publicMetadata, privateMetadata, celExpressions, constraints)) .andExpect(status().isOk()); assertThat(participantRepository.findById(participant.getId())).isPresent() @@ -199,7 +206,7 @@ void shouldUploadFile() throws Exception { } @Test - void shouldUploadFile_whenPolicyAndContractDefExist() throws Exception { + void shouldFailUploadFile_whenPolicyAndContractDefExist() throws Exception { // Create a tenant and participant var tenant = new Tenant(); tenant.setName("Test Tenant"); @@ -225,31 +232,30 @@ void shouldUploadFile_whenPolicyAndContractDefExist() throws Exception { ); // Create metadata - var metadataPart = new MockPart("metadata", "{\"foo\": \"bar\"}".getBytes()); - metadataPart.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var publicMetadata = new MockPart("publicMetadata", "{\"foo\": \"bar\"}".getBytes()); + publicMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); + var privateMetadata = new MockPart("privateMetadata", "{\"private\": \"value\"}".getBytes()); + privateMetadata.getHeaders().setContentType(MediaType.APPLICATION_JSON); - // mock create-cel-expression - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + // Mock the upload response from the dataplane + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .setBody("{\"id\": \"generated-file-id-123\"}") + .addHeader("Content-Type", "application/json")); // mock create-asset mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - // mock create-policy - mockWebServer.enqueue(new MockResponse().setResponseCode(409)); + // mock create-cel-expression (membership) + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - //mock create-contractdef + // mock create-policy mockWebServer.enqueue(new MockResponse().setResponseCode(409)); - // Mock the upload response from the dataplane - mockWebServer.enqueue(new MockResponse() - .setResponseCode(200) - .setBody("{\"id\": \"generated-file-id-123\"}") - .addHeader("Content-Type", "application/json")); - mockMvc.perform(multipart("/api/ui/service-providers/{providerId}/tenants/{tenantId}/participants/{participantId}/files", serviceProvider.getId(), tenant.getId(), participant.getId()) .file(mockFile) - .part(metadataPart)) + .part(publicMetadata, privateMetadata)) .andExpect(status().isInternalServerError()); } @@ -367,4 +373,4 @@ void shouldRequestContractWithConstraints() throws Exception { .content(objectMapper.writeValueAsString(contractRequest))) .andExpect(status().isOk()); } -} \ No newline at end of file +} diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index 076cef7..be47d5a 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -27,8 +27,10 @@ import com.metaformsystems.redline.domain.repository.TenantRepository; import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; import com.metaformsystems.redline.infrastructure.client.management.dto.ContractRequest; +import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; import com.metaformsystems.redline.infrastructure.client.management.dto.Obligation; import com.metaformsystems.redline.infrastructure.client.management.dto.Offer; +import com.metaformsystems.redline.infrastructure.client.management.dto.PolicySet; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; @@ -48,6 +50,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; @@ -153,6 +156,56 @@ void shouldRequestCatalog_andBypassCacheWithNoCache() { assertThat(mockWebServer.getRequestCount()).isEqualTo(2); } + @Test + void shouldUploadFileWithCelExpressionsAndConstraints() { + var participant = createAndSaveParticipant("ctx-upload-1", "did:web:me"); + + // custom CEL expression + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // dataplane upload response + mockWebServer.enqueue(new MockResponse() + .setBody("{\"id\": \"generated-file-id-123\"}") + .addHeader("Content-Type", "application/json")); + + // asset creation + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // membership CEL expression + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // policy creation + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + // contract definition + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + + var celExpressions = List.of(CelExpression.Builder.aNewCelExpression() + .id("custom-expression") + .leftOperand("CustomCredential") + .description("Custom expression") + .expression("true") + .scopes(Set.of("catalog")) + .build()); + + var constraints = List.of(new PolicySet.Constraint("purpose", "eq", "test")); + + dataAccessService.uploadFileForParticipant( + participant.getId(), + new java.util.HashMap<>(Map.of("foo", "bar")), + new java.util.HashMap<>(Map.of("private", "value")), + new java.io.ByteArrayInputStream("file-data".getBytes()), + "text/plain", + "file.txt", + celExpressions, + constraints + ); + + assertThat(participantRepository.findById(participant.getId())) + .isPresent() + .hasValueSatisfying(p -> assertThat(p.getUploadedFiles()).hasSize(1)); + } + @Test void shouldRequestCatalog_andRefreshWhenMaxAgeIsZero() { var participant = createAndSaveParticipant("ctx-3", "did:web:me"); From 1cb5a4579a6cccc0fbfa401614bfff612ca24c20 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 15:02:51 +0100 Subject: [PATCH 05/12] test: fix dataplane url --- .../domain/service/DataAccessServiceIntegrationTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index be47d5a..c14015e 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -94,6 +94,8 @@ static void configureProperties(DynamicPropertyRegistry registry) { registry.add("tenant-manager.url", () -> "http://%s:%s/tm".formatted(mockBackEndHost, mockBackEndPort)); registry.add("vault.url", () -> "http://%s:%s/vault".formatted(mockBackEndHost, mockBackEndPort)); registry.add("controlplane.url", () -> "http://%s:%s/cp".formatted(mockBackEndHost, mockBackEndPort)); + registry.add("dataplane.url", () -> "http://%s:%s/dataplane".formatted(mockBackEndHost, mockBackEndPort)); + registry.add("dataplane.internal.url", () -> "http://%s:%s/dataplane".formatted(mockBackEndHost, mockBackEndPort)); } @AfterEach From 675cd0843bb1496217331005917107b3f3668513 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Fri, 30 Jan 2026 15:10:02 +0100 Subject: [PATCH 06/12] chore: update license header --- .../redline/api/controller/EdcDataController.java | 1 + .../com/metaformsystems/redline/domain/service/Constants.java | 1 + .../redline/domain/service/DataAccessService.java | 1 + .../java/com/metaformsystems/redline/TransferEndToEndTest.java | 1 + .../redline/api/controller/EdcDataControllerTest.java | 1 + .../redline/domain/service/DataAccessServiceIntegrationTest.java | 1 + 6 files changed, 6 insertions(+) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index 57cc251..f8f6cfe 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - OpenAPI and file upload * */ diff --git a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java index 0d8187f..e116b4f 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/Constants.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/Constants.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - refactoring * */ diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 13fb2ef..46bd42d 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - CEL and policy creation for uploaded file * */ diff --git a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java index 30a80bf..be9df37 100644 --- a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java +++ b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - Add CEL and access constraint * */ diff --git a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java index b36b5c6..9b73322 100644 --- a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java +++ b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - improve tests * */ diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index c14015e..239a285 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -9,6 +9,7 @@ * * Contributors: * Metaform Systems, Inc. - initial API and implementation + * Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. - CEL and Constraint * */ From 68bd601c95c1a38805a3fea3647351761fe6ae79 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Mon, 2 Feb 2026 09:53:15 +0100 Subject: [PATCH 07/12] refactor: upload replace constraints with policy set --- .../api/controller/EdcDataController.java | 20 +++++------ .../domain/service/DataAccessService.java | 36 +++++++++++-------- .../redline/TransferEndToEndTest.java | 12 +++---- .../DataAccessServiceIntegrationTest.java | 5 +-- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index f8f6cfe..fdadd08 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -72,20 +72,20 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @PathVariable Long providerId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, - @RequestPart(value = "celExpressions", required = false) String celExpressions, - @RequestPart(value = "constraints", required = false) String constraints, + @RequestPart(value = "celExpressions", required = false) String celExpressionsString, + @RequestPart(value = "policySet", required = false) String policySetString, @RequestPart("file") MultipartFile file) { try { var publicMetadataMap = objectMapper.readValue(publicMetadata, new TypeReference>() {}); var privateMetadataMap = objectMapper.readValue(privateMetadata, new TypeReference>() {}); - List celExpressionList = null; - if (celExpressions != null) { - celExpressionList = objectMapper.readValue(celExpressions, new TypeReference<>() {}); + List celExpressions = null; + if (celExpressionsString != null) { + celExpressions = objectMapper.readValue(celExpressionsString, new TypeReference<>() {}); } - List constraintList = null; - if (constraints != null) { - constraintList = objectMapper.readValue(constraints, new TypeReference<>() {}); + PolicySet policySet = null; + if (policySetString != null) { + policySet = objectMapper.readValue(policySetString, new TypeReference<>() {}); } dataAccessService.uploadFileForParticipant( participantId, @@ -94,8 +94,8 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, file.getInputStream(), file.getContentType(), file.getOriginalFilename(), - celExpressionList, - constraintList + celExpressions, + policySet ); } catch (IOException e) { return ResponseEntity.internalServerError().build(); diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 46bd42d..8de7d5c 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -39,7 +39,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.metaformsystems.redline.domain.service.Constants.*; +import static com.metaformsystems.redline.domain.service.Constants.ASSET_PERMISSION; +import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_CONSTRAINT; +import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION; +import static com.metaformsystems.redline.domain.service.Constants.MEMBERSHIP_EXPRESSION_ID; @Service public class DataAccessService { @@ -59,7 +62,7 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, List constraints) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet policySet) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); @@ -104,23 +107,28 @@ public void uploadFileForParticipant(Long participantId, Map pub } //2. create policy + if (policySet != null) { + var constraints = new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)); + constraints.addAll(policySet.getPermission().getFirst().getConstraint()); + policySet.getPermission().getFirst().setConstraint(constraints); + } else { + policySet = new PolicySet(List.of(new PolicySet.Permission("use", + new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)) + ))); + } var policy = NewPolicyDefinition.Builder.aNewPolicyDefinition() .id(UUID.randomUUID().toString()) - .policy(new PolicySet(List.of(new PolicySet.Permission("use", - new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)) - )))).build(); - if (constraints != null) { - policy.getPolicy().getPermission().getFirst().getConstraint().addAll(constraints); - } + .policy(policySet).build(); managementApiClient.createPolicy(participantContextId, policy); //3. create contract definition if none exists - var contractDef = NewContractDefinition.Builder.aNewContractDefinition(); - contractDef.id(UUID.randomUUID().toString()); - contractDef.contractPolicyId(policy.getId()); - contractDef.accessPolicyId(policy.getId()); - contractDef.assetsSelector(Set.of(new Criterion("id", "=", assetId))); - managementApiClient.createContractDefinition(participantContextId, contractDef.build()); + var contractDef = NewContractDefinition.Builder.aNewContractDefinition() + .id(UUID.randomUUID().toString()) + .contractPolicyId(policy.getId()) + .accessPolicyId(policy.getId()) + .assetsSelector(Set.of(new Criterion("id", "=", assetId))) + .build(); + managementApiClient.createContractDefinition(participantContextId, contractDef); //2. track uploaded file in DB diff --git a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java index be9df37..5c5b86e 100644 --- a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java +++ b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java @@ -25,10 +25,7 @@ import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.api.dto.response.Participant; import com.metaformsystems.redline.api.dto.response.Tenant; -import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; -import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; -import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; -import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; +import com.metaformsystems.redline.infrastructure.client.management.dto.*; import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import org.junit.jupiter.api.Test; @@ -50,7 +47,7 @@ /** * This test runs through a full participant deployment for consumer and provider plus a data transfer between them. - * For this test a running instance of JAD is required, and the Redline API Server must be reachable at http://redline.localhost. + * For this test a running instance of JAD is required, and the Redline API Server must be reachable at http://redline.localhost:8080. */ @EnabledIfEnvironmentVariable(named = "ENABLE_E2E_TESTS", matches = "true", disabledReason = "This can only run if ENABLE_E2E_TESTS=true is set in the environment.") public class TransferEndToEndTest { @@ -90,14 +87,15 @@ void testTransferFile() throws Exception { .expression("ctx.agent.id == this.rightOperand") .scopes(Set.of("catalog", "contract.negotiation", "transfer.process")) .build()); - var constraints = List.of(new Constraint("CounterPartyId", "eq", consumerDid)); + var policySet = new PolicySet(List.of(new PolicySet.Permission("use", + List.of(new PolicySet.Constraint("CounterPartyId", "eq", consumerDid))))); baseRequest() .contentType(ContentType.MULTIPART) .multiPart("file", "testfile.txt", "This is a test file.".getBytes()) .multiPart("publicMetadata", "{\"slug\": \"%s\"}".formatted(slug), "application/json") .multiPart("privateMetadata", "{\"privateSlug\": \"%s\"}".formatted(slug), "application/json") .multiPart("celExpressions", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(celExpressions), "application/json") - .multiPart("constraints", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(constraints), "application/json") + .multiPart("constraints", new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(policySet), "application/json") .post("/api/ui/service-providers/%s/tenants/%s/participants/%s/files".formatted(SERVICE_PROVIDER_ID, provider.tenantId(), provider.participantId())) .then() .statusCode(200); diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index 239a285..713c83a 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -191,7 +191,8 @@ void shouldUploadFileWithCelExpressionsAndConstraints() { .scopes(Set.of("catalog")) .build()); - var constraints = List.of(new PolicySet.Constraint("purpose", "eq", "test")); + var policySet = new PolicySet(List.of(new PolicySet.Permission("use", + List.of(new PolicySet.Constraint("purpose", "eq", "test"))))); dataAccessService.uploadFileForParticipant( participant.getId(), @@ -201,7 +202,7 @@ void shouldUploadFileWithCelExpressionsAndConstraints() { "text/plain", "file.txt", celExpressions, - constraints + policySet ); assertThat(participantRepository.findById(participant.getId())) From 3b82569249f5f53a200a8ee81966cf1708c58424 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Mon, 2 Feb 2026 11:14:56 +0100 Subject: [PATCH 08/12] refactor: controller policy set type --- .../redline/api/controller/EdcDataController.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index fdadd08..e371822 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -73,7 +73,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @RequestPart("publicMetadata") String publicMetadata, @RequestPart("privateMetadata") String privateMetadata, @RequestPart(value = "celExpressions", required = false) String celExpressionsString, - @RequestPart(value = "policySet", required = false) String policySetString, + @RequestPart(value = "policySet", required = false) PolicySet policySet, @RequestPart("file") MultipartFile file) { try { @@ -83,10 +83,6 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, if (celExpressionsString != null) { celExpressions = objectMapper.readValue(celExpressionsString, new TypeReference<>() {}); } - PolicySet policySet = null; - if (policySetString != null) { - policySet = objectMapper.readValue(policySetString, new TypeReference<>() {}); - } dataAccessService.uploadFileForParticipant( participantId, publicMetadataMap, From 86795446161fef6ca712ae52c5ac75ace8883f3c Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Mon, 2 Feb 2026 15:48:28 +0100 Subject: [PATCH 09/12] fix: wildcard imports --- .../api/controller/EdcDataController.java | 19 +++++++++++++++++-- .../domain/service/DataAccessService.java | 19 +++++++++++++++++-- .../redline/TransferEndToEndTest.java | 6 +++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index e371822..dec454f 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -24,7 +24,15 @@ import com.metaformsystems.redline.api.dto.response.ContractNegotiation; import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.domain.service.DataAccessService; -import com.metaformsystems.redline.infrastructure.client.management.dto.*; +import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; +import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; +import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; +import com.metaformsystems.redline.infrastructure.client.management.dto.Obligation; +import com.metaformsystems.redline.infrastructure.client.management.dto.Offer; +import com.metaformsystems.redline.infrastructure.client.management.dto.Permission; +import com.metaformsystems.redline.infrastructure.client.management.dto.PolicySet; +import com.metaformsystems.redline.infrastructure.client.management.dto.Prohibition; +import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -34,7 +42,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 8de7d5c..cfc6edf 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -16,13 +16,23 @@ package com.metaformsystems.redline.domain.service; import com.metaformsystems.redline.api.dto.request.TransferProcessRequest; +import com.metaformsystems.redline.infrastructure.client.management.dto.ContractRequest; import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.domain.entity.UploadedFile; import com.metaformsystems.redline.domain.exception.ObjectNotFoundException; import com.metaformsystems.redline.domain.repository.ParticipantRepository; import com.metaformsystems.redline.infrastructure.client.dataplane.DataPlaneApiClient; import com.metaformsystems.redline.infrastructure.client.management.ManagementApiClient; -import com.metaformsystems.redline.infrastructure.client.management.dto.*; +import com.metaformsystems.redline.infrastructure.client.management.dto.Asset; +import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; +import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; +import com.metaformsystems.redline.infrastructure.client.management.dto.ContractNegotiation; +import com.metaformsystems.redline.infrastructure.client.management.dto.Criterion; +import com.metaformsystems.redline.infrastructure.client.management.dto.NewContractDefinition; +import com.metaformsystems.redline.infrastructure.client.management.dto.NewPolicyDefinition; +import com.metaformsystems.redline.infrastructure.client.management.dto.PolicySet; +import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; +import com.metaformsystems.redline.infrastructure.client.management.dto.TransferRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -34,7 +44,12 @@ import java.io.InputStream; import java.time.Duration; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java index 5c5b86e..2c092ae 100644 --- a/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java +++ b/src/test/java/com/metaformsystems/redline/TransferEndToEndTest.java @@ -25,7 +25,11 @@ import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.api.dto.response.Participant; import com.metaformsystems.redline.api.dto.response.Tenant; -import com.metaformsystems.redline.infrastructure.client.management.dto.*; +import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; +import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; +import com.metaformsystems.redline.infrastructure.client.management.dto.Constraint; +import com.metaformsystems.redline.infrastructure.client.management.dto.PolicySet; +import com.metaformsystems.redline.infrastructure.client.management.dto.TransferProcess; import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import org.junit.jupiter.api.Test; From be76661fe0a9b1dc5c6c7eccb657f8e378f85d1d Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Mon, 2 Feb 2026 16:29:51 +0100 Subject: [PATCH 10/12] refactor: upload part actual types --- .../api/controller/EdcDataController.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index dec454f..b2f9143 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -85,23 +85,16 @@ public EdcDataController(DataAccessService dataAccessService, ObjectMapper objec public ResponseEntity uploadFile(@PathVariable Long participantId, @PathVariable Long tenantId, @PathVariable Long providerId, - @RequestPart("publicMetadata") String publicMetadata, - @RequestPart("privateMetadata") String privateMetadata, - @RequestPart(value = "celExpressions", required = false) String celExpressionsString, + @RequestPart("publicMetadata") Map publicMetadata, + @RequestPart("privateMetadata") Map privateMetadata, + @RequestPart(value = "celExpressions", required = false) List celExpressions, @RequestPart(value = "policySet", required = false) PolicySet policySet, @RequestPart("file") MultipartFile file) { - try { - var publicMetadataMap = objectMapper.readValue(publicMetadata, new TypeReference>() {}); - var privateMetadataMap = objectMapper.readValue(privateMetadata, new TypeReference>() {}); - List celExpressions = null; - if (celExpressionsString != null) { - celExpressions = objectMapper.readValue(celExpressionsString, new TypeReference<>() {}); - } dataAccessService.uploadFileForParticipant( participantId, - publicMetadataMap, - privateMetadataMap, + publicMetadata, + privateMetadata, file.getInputStream(), file.getContentType(), file.getOriginalFilename(), From 65dae6dedbfce9cd4c8a315f7cc8500750c54154 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Tue, 3 Feb 2026 09:37:19 +0100 Subject: [PATCH 11/12] refactor: cel expression creation --- .../api/controller/EdcDataController.java | 8 +-- .../domain/service/DataAccessService.java | 54 ++++++++----------- .../api/controller/EdcDataControllerTest.java | 6 +-- .../DataAccessServiceIntegrationTest.java | 10 ++-- 4 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index b2f9143..bbd0844 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -54,8 +54,10 @@ import java.io.IOException; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; @RestController @Tag(name = "EDC data operations", description = "UI API for uploading and downloading data, managing EDC data transfers, and related operations") @@ -87,8 +89,8 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @PathVariable Long providerId, @RequestPart("publicMetadata") Map publicMetadata, @RequestPart("privateMetadata") Map privateMetadata, - @RequestPart(value = "celExpressions", required = false) List celExpressions, - @RequestPart(value = "policySet", required = false) PolicySet policySet, + @RequestPart(name = "celExpressions", required = false) Optional> celExpressions, + @RequestPart(name = "policySet", required = false) PolicySet policySet, @RequestPart("file") MultipartFile file) { try { dataAccessService.uploadFileForParticipant( @@ -98,7 +100,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, file.getInputStream(), file.getContentType(), file.getOriginalFilename(), - celExpressions, + celExpressions.orElseGet(ArrayList::new), policySet ); } catch (IOException e) { diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index cfc6edf..563ba63 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -16,7 +16,6 @@ package com.metaformsystems.redline.domain.service; import com.metaformsystems.redline.api.dto.request.TransferProcessRequest; -import com.metaformsystems.redline.infrastructure.client.management.dto.ContractRequest; import com.metaformsystems.redline.api.dto.response.FileResource; import com.metaformsystems.redline.domain.entity.UploadedFile; import com.metaformsystems.redline.domain.exception.ObjectNotFoundException; @@ -27,6 +26,7 @@ import com.metaformsystems.redline.infrastructure.client.management.dto.Catalog; import com.metaformsystems.redline.infrastructure.client.management.dto.CelExpression; import com.metaformsystems.redline.infrastructure.client.management.dto.ContractNegotiation; +import com.metaformsystems.redline.infrastructure.client.management.dto.ContractRequest; import com.metaformsystems.redline.infrastructure.client.management.dto.Criterion; import com.metaformsystems.redline.infrastructure.client.management.dto.NewContractDefinition; import com.metaformsystems.redline.infrastructure.client.management.dto.NewPolicyDefinition; @@ -77,23 +77,11 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet policySet) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, ArrayList celExpressions, PolicySet policySet) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); - - //-1. create CEL expressions - if (celExpressions != null) { - celExpressions.forEach(celExpression -> { - try { - managementApiClient.createCelExpression(celExpression); - } catch (WebClientResponseException.Conflict e) { - //do nothing, CEL expression already exists - } - }); - } - //0. upload file to data plane var assetId = UUID.randomUUID().toString(); publicMetadata.put("assetId", assetId); @@ -102,26 +90,28 @@ public void uploadFileForParticipant(Long participantId, Map pub var response = dataPlaneApiClient.uploadMultipart(participantContextId, combinedMetadata, fileStream); var fileId = response.id(); - //1. create asset - publicMetadata.put("fileId", fileId); + //1. create CEL expressions + celExpressions.add(CelExpression.Builder.aNewCelExpression() + .id(MEMBERSHIP_EXPRESSION_ID) + .leftOperand("MembershipCredential") + .description("Expression for evaluating membership credential") + .scopes(Set.of("catalog", "contract.negotiation", "transfer.process")) + .expression(MEMBERSHIP_EXPRESSION) + .build()); + celExpressions.forEach(celExpression -> { + try { + managementApiClient.createCelExpression(celExpression); + } catch (WebClientResponseException.Conflict e) { + //do nothing, CEL expression already exists + } + }); + //2. create asset + publicMetadata.put("fileId", fileId); var asset = createAsset(assetId, publicMetadata, privateMetadata, contentType, originalFilename); managementApiClient.createAsset(participantContextId, asset); - // create CEL expression - try { - managementApiClient.createCelExpression(CelExpression.Builder.aNewCelExpression() - .id(MEMBERSHIP_EXPRESSION_ID) - .leftOperand("MembershipCredential") - .description("Expression for evaluating membership credential") - .scopes(Set.of("catalog", "contract.negotiation", "transfer.process")) - .expression(MEMBERSHIP_EXPRESSION) - .build()); - } catch (WebClientResponseException.Conflict e) { - //do nothing, CEL expression already exists - } - - //2. create policy + //3. create policy if (policySet != null) { var constraints = new ArrayList<>(List.of(MEMBERSHIP_CONSTRAINT)); constraints.addAll(policySet.getPermission().getFirst().getConstraint()); @@ -136,7 +126,7 @@ public void uploadFileForParticipant(Long participantId, Map pub .policy(policySet).build(); managementApiClient.createPolicy(participantContextId, policy); - //3. create contract definition if none exists + //4. create contract definition if none exists var contractDef = NewContractDefinition.Builder.aNewContractDefinition() .id(UUID.randomUUID().toString()) .contractPolicyId(policy.getId()) @@ -146,7 +136,7 @@ public void uploadFileForParticipant(Long participantId, Map pub managementApiClient.createContractDefinition(participantContextId, contractDef); - //2. track uploaded file in DB + //5. track uploaded file in DB participant.getUploadedFiles().add(new UploadedFile(fileId, originalFilename, contentType, combinedMetadata)); } diff --git a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java index 9b73322..cb2c053 100644 --- a/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java +++ b/src/test/java/com/metaformsystems/redline/api/controller/EdcDataControllerTest.java @@ -174,15 +174,15 @@ void shouldUploadFile() throws Exception { var constraints = new MockPart("constraints", "[{\"leftOperand\":\"purpose\",\"operator\":\"eq\",\"rightOperand\":\"test\"}]".getBytes()); constraints.getHeaders().setContentType(MediaType.APPLICATION_JSON); - // mock create-cel-expression (custom) - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - // Mock the upload response from the dataplane mockWebServer.enqueue(new MockResponse() .setResponseCode(200) .setBody("{\"id\": \"generated-file-id-123\"}") .addHeader("Content-Type", "application/json")); + // mock create-cel-expression (custom) + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + // mock create-asset mockWebServer.enqueue(new MockResponse().setResponseCode(200)); diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index 713c83a..784ab39 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -163,14 +163,14 @@ void shouldRequestCatalog_andBypassCacheWithNoCache() { void shouldUploadFileWithCelExpressionsAndConstraints() { var participant = createAndSaveParticipant("ctx-upload-1", "did:web:me"); - // custom CEL expression - mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - // dataplane upload response mockWebServer.enqueue(new MockResponse() .setBody("{\"id\": \"generated-file-id-123\"}") .addHeader("Content-Type", "application/json")); + // custom CEL expression + mockWebServer.enqueue(new MockResponse().setResponseCode(200)); + // asset creation mockWebServer.enqueue(new MockResponse().setResponseCode(200)); @@ -183,13 +183,13 @@ void shouldUploadFileWithCelExpressionsAndConstraints() { // contract definition mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - var celExpressions = List.of(CelExpression.Builder.aNewCelExpression() + var celExpressions = new ArrayList<>(List.of(CelExpression.Builder.aNewCelExpression() .id("custom-expression") .leftOperand("CustomCredential") .description("Custom expression") .expression("true") .scopes(Set.of("catalog")) - .build()); + .build())); var policySet = new PolicySet(List.of(new PolicySet.Permission("use", List.of(new PolicySet.Constraint("purpose", "eq", "test"))))); From 7c5c18cb0e601afdd83e66d195432a2f45c6d727 Mon Sep 17 00:00:00 2001 From: Tim Dahlmanns Date: Tue, 3 Feb 2026 10:34:16 +0100 Subject: [PATCH 12/12] revert: cel expressions type --- .../redline/api/controller/EdcDataController.java | 8 +++----- .../redline/domain/service/DataAccessService.java | 7 ++++--- .../domain/service/DataAccessServiceIntegrationTest.java | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java index bbd0844..eb914f0 100644 --- a/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java +++ b/src/main/java/com/metaformsystems/redline/api/controller/EdcDataController.java @@ -54,10 +54,8 @@ import java.io.IOException; import java.time.Instant; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; @RestController @Tag(name = "EDC data operations", description = "UI API for uploading and downloading data, managing EDC data transfers, and related operations") @@ -89,8 +87,8 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, @PathVariable Long providerId, @RequestPart("publicMetadata") Map publicMetadata, @RequestPart("privateMetadata") Map privateMetadata, - @RequestPart(name = "celExpressions", required = false) Optional> celExpressions, - @RequestPart(name = "policySet", required = false) PolicySet policySet, + @RequestPart(value = "celExpressions", required = false) List celExpressions, + @RequestPart(value = "policySet", required = false) PolicySet policySet, @RequestPart("file") MultipartFile file) { try { dataAccessService.uploadFileForParticipant( @@ -100,7 +98,7 @@ public ResponseEntity uploadFile(@PathVariable Long participantId, file.getInputStream(), file.getContentType(), file.getOriginalFilename(), - celExpressions.orElseGet(ArrayList::new), + celExpressions != null ? celExpressions : List.of(), policySet ); } catch (IOException e) { diff --git a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java index 563ba63..e09f28f 100644 --- a/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java +++ b/src/main/java/com/metaformsystems/redline/domain/service/DataAccessService.java @@ -77,7 +77,7 @@ public DataAccessService(DataPlaneApiClient dataPlaneApiClient, WebDidResolver w } @Transactional - public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, ArrayList celExpressions, PolicySet policySet) { + public void uploadFileForParticipant(Long participantId, Map publicMetadata, Map privateMetadata, InputStream fileStream, String contentType, String originalFilename, List celExpressions, PolicySet policySet) { var participant = participantRepository.findById(participantId).orElseThrow(() -> new ObjectNotFoundException("Participant not found with id: " + participantId)); var participantContextId = participant.getParticipantContextId(); @@ -91,14 +91,15 @@ public void uploadFileForParticipant(Long participantId, Map pub var fileId = response.id(); //1. create CEL expressions - celExpressions.add(CelExpression.Builder.aNewCelExpression() + var expressions = new ArrayList<>(celExpressions); + expressions.add(CelExpression.Builder.aNewCelExpression() .id(MEMBERSHIP_EXPRESSION_ID) .leftOperand("MembershipCredential") .description("Expression for evaluating membership credential") .scopes(Set.of("catalog", "contract.negotiation", "transfer.process")) .expression(MEMBERSHIP_EXPRESSION) .build()); - celExpressions.forEach(celExpression -> { + expressions.forEach(celExpression -> { try { managementApiClient.createCelExpression(celExpression); } catch (WebClientResponseException.Conflict e) { diff --git a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java index 784ab39..0ab6541 100644 --- a/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java +++ b/src/test/java/com/metaformsystems/redline/domain/service/DataAccessServiceIntegrationTest.java @@ -183,13 +183,13 @@ void shouldUploadFileWithCelExpressionsAndConstraints() { // contract definition mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - var celExpressions = new ArrayList<>(List.of(CelExpression.Builder.aNewCelExpression() + var celExpressions = List.of(CelExpression.Builder.aNewCelExpression() .id("custom-expression") .leftOperand("CustomCredential") .description("Custom expression") .expression("true") .scopes(Set.of("catalog")) - .build())); + .build()); var policySet = new PolicySet(List.of(new PolicySet.Permission("use", List.of(new PolicySet.Constraint("purpose", "eq", "test")))));