Skip to content

Commit

Permalink
Merge branch 'feature/440645-transferencia-inesdata' into 'develop'
Browse files Browse the repository at this point in the history
Feature/440645 transferencia inesdata

See merge request upm-inesdata/inesdata-connector!34
  • Loading branch information
Pablo Pérez López committed Aug 21, 2024
2 parents e88a279 + 69c3518 commit b69f6e4
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 0 deletions.
3 changes: 3 additions & 0 deletions extensions/inesdata-transfer-process-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# InesData Transfer Process API
Provides a management API to handle InesData Transfer Process entities. This API expands the functionality of the existing control-plane management API by introducing a new endpoint for the initialization of data transfers to the connector's own MinIO storage.

25 changes: 25 additions & 0 deletions extensions/inesdata-transfer-process-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
`java-library`
id("com.gmv.inesdata.edc-application")
}

dependencies {
api(libs.edc.spi.core)
implementation(libs.edc.transfer.process.api)
implementation(libs.edc.api.management.lib)
implementation(libs.edc.web.spi)

implementation(libs.edc.connector.core)
implementation(libs.edc.api.core)
implementation(libs.edc.lib.util)
implementation(libs.edc.lib.transform)
implementation(libs.edc.dsp.api.configuration)
implementation(libs.edc.api.management.config)
implementation(libs.swagger.annotations.jakarta)
implementation(libs.edc.transaction.spi)
implementation(libs.edc.lib.validator)
implementation(libs.edc.validator.spi)
implementation(libs.swagger.annotations.jakarta)
runtimeOnly(libs.edc.spi.jsonld)
runtimeOnly(libs.edc.json.ld.lib)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.upm.inesdata.inesdatatransfer;

import jakarta.json.Json;
import jakarta.json.JsonBuilderFactory;
import org.eclipse.edc.connector.controlplane.api.management.transferprocess.transform.JsonObjectFromTransferProcessTransformer;
import org.eclipse.edc.connector.controlplane.api.management.transferprocess.transform.JsonObjectFromTransferStateTransformer;
import org.eclipse.edc.connector.controlplane.api.management.transferprocess.transform.JsonObjectToSuspendTransferTransformer;
import org.eclipse.edc.connector.controlplane.api.management.transferprocess.transform.JsonObjectToTerminateTransferTransformer;
import org.eclipse.edc.connector.controlplane.api.management.transferprocess.transform.JsonObjectToTransferRequestTransformer;
import org.eclipse.edc.connector.controlplane.api.management.transferprocess.validation.TerminateTransferValidator;
import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.security.Vault;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;
import org.eclipse.edc.web.spi.WebService;
import org.upm.inesdata.inesdatatransfer.controller.InesdataTransferProcessApiController;
import org.upm.inesdata.inesdatatransfer.validations.InesdataTransferRequestValidator;

import java.util.Collections;

@Extension("Management API: Inesdata Transfer Process")
public class InesdataTransferProcessApiExtension implements ServiceExtension {
public static final String NAME = "Management API: Transfer Process";
public static final String DEFAULT_VALUE = "";
public static final String AWS_ACCESS_KEY = "edc.aws.access.key";
public static final String AWS_SECRET_ACCESS = "edc.aws.secret.access.key";
public static final String AWS_ENDPOINT_OVERRIDE = "edc.aws.endpoint.override";
public static final String AWS_REGION = "edc.aws.region";
public static final String AWS_BUCKET_NAME = "edc.aws.bucket.name";


@Inject
private WebService webService;
@Inject
private TypeTransformerRegistry transformerRegistry;
@Inject
private TransferProcessService service;
@Inject
private JsonObjectValidatorRegistry validatorRegistry;
@Inject
private Vault vault;

public InesdataTransferProcessApiExtension() {
}

public String name() {
return "Management API: Inesdata Transfer Process";
}

public void initialize(ServiceExtensionContext context) {
JsonBuilderFactory builderFactory = Json.createBuilderFactory(Collections.emptyMap());
TypeTransformerRegistry managementApiTransformerRegistry = this.transformerRegistry.forContext("management-api");
managementApiTransformerRegistry.register(new JsonObjectFromTransferProcessTransformer(builderFactory));
managementApiTransformerRegistry.register(new JsonObjectFromTransferStateTransformer(builderFactory));
managementApiTransformerRegistry.register(new JsonObjectToTerminateTransferTransformer());
managementApiTransformerRegistry.register(new JsonObjectToSuspendTransferTransformer());
managementApiTransformerRegistry.register(new JsonObjectToTransferRequestTransformer());
// Leer las variables de entorno
var accessKey = vault.resolveSecret(context.getSetting(AWS_ACCESS_KEY, DEFAULT_VALUE));
var secretKey = vault.resolveSecret(context.getSetting(AWS_SECRET_ACCESS, DEFAULT_VALUE));
var endpointOverride = context.getSetting(AWS_ENDPOINT_OVERRIDE, DEFAULT_VALUE);
var regionName = context.getSetting(AWS_REGION, DEFAULT_VALUE);
var bucketName = context.getSetting(AWS_BUCKET_NAME, DEFAULT_VALUE);

this.validatorRegistry.register("https://w3id.org/edc/v0.0.1/ns/TransferRequest", InesdataTransferRequestValidator.instance(context.getMonitor()));
this.validatorRegistry.register("https://w3id.org/edc/v0.0.1/ns/TerminateTransfer", TerminateTransferValidator.instance());
this.webService.registerResource("management", new InesdataTransferProcessApiController(context.getMonitor(), this.service, managementApiTransformerRegistry, this.validatorRegistry, bucketName, regionName, accessKey, secretKey, endpointOverride));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package org.upm.inesdata.inesdatatransfer.controller;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.links.Link;
import io.swagger.v3.oas.annotations.links.LinkParameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import org.eclipse.edc.api.management.schema.ManagementApiSchema;
import org.eclipse.edc.api.model.ApiCoreSchema;
import org.eclipse.edc.connector.controlplane.api.management.transferprocess.v3.TransferProcessApiV3;

import java.util.List;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;

@OpenAPIDefinition(
info = @Info(
version = "v3"
)
)
@Tag(
name = "Transfer Process V3"
)
public interface InesdataTransferProcessApi {
String ASYNC_WARNING = "Due to the asynchronous nature of transfers, a successful response only indicates that the request was successfully received. This may take a long time, so clients must poll the /{id}/state endpoint to track the state.";

@Operation(
description = "Initiates a data transfer with the given parameters. Due to the asynchronous nature of transfers, a successful response only indicates that the request was successfully received. This may take a long time, so clients must poll the /{id}/state endpoint to track the state.",
requestBody = @RequestBody(
content = {@Content(
schema = @Schema(
implementation = TransferRequestSchema.class
)
)}
),
responses = {@ApiResponse(
responseCode = "200",
description = "The transfer was successfully initiated. Returns the transfer process ID and created timestamp",
content = {@Content(
schema = @Schema(
implementation = ApiCoreSchema.IdResponseSchema.class
)
)},
links = {@Link(
name = "poll-state",
operationId = "getTransferProcessStateV3",
parameters = {@LinkParameter(
name = "id",
expression = "$response.body#/id"
)}
)}
), @ApiResponse(
responseCode = "400",
description = "Request body was malformed",
content = {@Content(
array = @ArraySchema(
schema = @Schema(
implementation = ApiCoreSchema.ApiErrorDetailSchema.class
)
)
)}
)}
)
JsonObject initiateTransferProcess(JsonObject var1);


@Schema(name = "TransferRequest", example = TransferProcessApiV3.TransferRequestSchema.TRANSFER_REQUEST_EXAMPLE)
record TransferRequestSchema(
@Schema(name = CONTEXT, requiredMode = REQUIRED)
Object context,
@Schema(name = TYPE, example = TRANSFER_REQUEST_TYPE)
String type,
@Schema(requiredMode = REQUIRED)
String protocol,
@Schema(requiredMode = REQUIRED)
String counterPartyAddress,
@Schema(requiredMode = REQUIRED)
String contractId,
@Schema(deprecated = true)
String assetId,
@Schema(requiredMode = REQUIRED)
String transferType,
ApiCoreSchema.DataAddressSchema dataDestination,
@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
ManagementApiSchema.FreeFormPropertiesSchema privateProperties,
List<ManagementApiSchema.CallbackAddressSchema> callbackAddresses) {

public static final String TRANSFER_REQUEST_EXAMPLE = """
{
"@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" },
"@type": "https://w3id.org/edc/v0.0.1/ns/TransferRequest",
"protocol": "dataspace-protocol-http",
"counterPartyAddress": "http://provider-address",
"contractId": "contract-id",
"transferType": "transferType",
"dataDestination": {
"type": "data-destination-type"
},
"privateProperties": {
"private-key": "private-value"
},
"callbackAddresses": [{
"transactional": false,
"uri": "http://callback/url",
"events": ["contract.negotiation", "transfer.process"],
"authKey": "auth-key",
"authCodeId": "auth-code-id"
}]
}
""";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.upm.inesdata.inesdatatransfer.controller;

import jakarta.json.JsonObject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import org.eclipse.edc.api.model.IdResponse;
import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService;
import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess;
import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.constants.CoreConstants;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;
import org.eclipse.edc.web.spi.exception.InvalidRequestException;
import org.eclipse.edc.web.spi.exception.ValidationFailureException;

import java.util.HashMap;
import java.util.Map;

import static java.lang.String.format;
import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_TYPE;
import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.mapToException;

@Consumes({ "application/json" })
@Produces({ "application/json" })
@Path("/v3/inesdatatransferprocesses")
public class InesdataTransferProcessApiController implements InesdataTransferProcessApi {

protected final Monitor monitor;
private final TransferProcessService service;
private final TypeTransformerRegistry transformerRegistry;
private final JsonObjectValidatorRegistry validatorRegistry;
private final String bucketName;
private final String region;
private final String accessKey;
private final String secretKey;
private final String endpointOverride;

public InesdataTransferProcessApiController(Monitor monitor, TransferProcessService service,
TypeTransformerRegistry transformerRegistry, JsonObjectValidatorRegistry validatorRegistry, String bucketName,
String region, String accessKey, String secretKey, String endpointOverride) {
this.monitor = monitor;
this.service = service;
this.transformerRegistry = transformerRegistry;
this.validatorRegistry = validatorRegistry;
this.bucketName = bucketName;
this.region = region;
this.accessKey = accessKey;
this.secretKey = secretKey;
this.endpointOverride = endpointOverride;
}

@POST
public JsonObject initiateTransferProcess(JsonObject request) {
validatorRegistry.validate(TRANSFER_REQUEST_TYPE, request).orElseThrow(ValidationFailureException::new);

var transferRequest = transformerRegistry.transform(request, TransferRequest.class)
.orElseThrow(InvalidRequestException::new);

DataAddress dataDestination = getDataDestinationProperties();

var tRequest = TransferRequest.Builder.newInstance().id(transferRequest.getId())
.transferType(transferRequest.getTransferType()).callbackAddresses(transferRequest.getCallbackAddresses())
.contractId(transferRequest.getContractId()).counterPartyAddress(transferRequest.getCounterPartyAddress())
.protocol(transferRequest.getProtocol()).privateProperties(transferRequest.getPrivateProperties())
.dataDestination(dataDestination).build();

var createdTransfer = service.initiateTransfer(tRequest)
.onSuccess(d -> monitor.debug(format("Transfer Process created %s", d.getId())))
.orElseThrow(it -> mapToException(it, TransferProcess.class));

var responseDto = IdResponse.Builder.newInstance().id(createdTransfer.getId())
.createdAt(createdTransfer.getCreatedAt()).build();

return transformerRegistry.transform(responseDto, JsonObject.class)
.orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail()));
}

private DataAddress getDataDestinationProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put(CoreConstants.EDC_NAMESPACE + "bucketName", bucketName);
properties.put(CoreConstants.EDC_NAMESPACE + "region", region);
properties.put(CoreConstants.EDC_NAMESPACE + "type", "AmazonS3");
properties.put(CoreConstants.EDC_NAMESPACE + "endpointOverride", endpointOverride);
properties.put(CoreConstants.EDC_NAMESPACE + "accessKeyId", accessKey);
properties.put(CoreConstants.EDC_NAMESPACE + "secretAccessKey", secretKey);
return DataAddress.Builder.newInstance().properties(properties).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.upm.inesdata.inesdatatransfer.validations;

import jakarta.json.JsonObject;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.validator.jsonobject.JsonObjectValidator;
import org.eclipse.edc.validator.jsonobject.validators.LogDeprecatedValue;
import org.eclipse.edc.validator.jsonobject.validators.MandatoryValue;
import org.eclipse.edc.validator.jsonobject.validators.OptionalIdNotBlank;
import org.eclipse.edc.validator.spi.Validator;

import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_ASSET_ID;
import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_CONTRACT_ID;
import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_COUNTER_PARTY_ADDRESS;
import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_PROTOCOL;
import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_TRANSFER_TYPE;

public class InesdataTransferRequestValidator {

public static Validator<JsonObject> instance(Monitor monitor) {
return JsonObjectValidator.newValidator()
.verifyId(OptionalIdNotBlank::new)
.verify(TRANSFER_REQUEST_ASSET_ID, path -> new LogDeprecatedValue(path, TRANSFER_REQUEST_ASSET_ID, "no attribute, as %s already provide such information".formatted(TRANSFER_REQUEST_CONTRACT_ID), monitor))
.verify(TRANSFER_REQUEST_COUNTER_PARTY_ADDRESS, MandatoryValue::new)
.verify(TRANSFER_REQUEST_CONTRACT_ID, MandatoryValue::new)
.verify(TRANSFER_REQUEST_PROTOCOL, MandatoryValue::new)
.verify(TRANSFER_REQUEST_TRANSFER_TYPE, MandatoryValue::new)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.upm.inesdata.inesdatatransfer.InesdataTransferProcessApiExtension
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lombok = "1.18.30"
edc-api-asset = { module = "org.eclipse.edc:asset-api", version.ref = "edc" }
edc-api-core = { module = "org.eclipse.edc:api-core", version.ref = "edc" }
edc-api-management-config = { module = "org.eclipse.edc:management-api-configuration", version.ref = "edc" }
edc-api-management-lib = { module = "org.eclipse.edc:management-api-lib", version.ref = "edc" }
edc-api-control-config = { module = "org.eclipse.edc:control-api-configuration", version.ref = "edc" }
edc-auth-spi = { module = "org.eclipse.edc:auth-spi", version.ref = "edc" }
edc-build-plugin = { module = "org.eclipse.edc.edc-build:org.eclipse.edc.edc-build.gradle.plugin", version.ref = "edc" }
Expand Down Expand Up @@ -84,6 +85,7 @@ edc-sql-contract-definition-store = { module = "org.eclipse.edc:contract-definit
edc-sql-contract-negotiation-store = { module = "org.eclipse.edc:contract-negotiation-store-sql", version.ref = "edc" }
edc-sql-policy-definition-store = { module = "org.eclipse.edc:policy-definition-store-sql", version.ref = "edc" }
edc-sql-transfer-process-store = { module = "org.eclipse.edc:transfer-process-store-sql", version.ref = "edc" }
edc-transfer-process-api = { module = "org.eclipse.edc:transfer-process-api", version.ref = "edc" }
edc-sql-data-plane-store = { module = "org.eclipse.edc:data-plane-store-sql", version.ref = "edc" }

# EDC aws s3 stuff
Expand Down
1 change: 1 addition & 0 deletions launchers/connector/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ dependencies {
implementation(project(":extensions:vocabulary-api"))
implementation(project(":extensions:vocabulary-shared-api"))
implementation(project(":extensions:vocabulary-shared-retrieval"))
implementation(project(":extensions:inesdata-transfer-process-api"))

// Policies
implementation(project(":extensions:policy-always-true"))
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ include(":extensions:dsp-vocabulary-http")
include(":extensions:shared-api-configuration")
include(":extensions:vocabulary-shared-api")
include(":extensions:vocabulary-shared-retrieval")
include(":extensions:inesdata-transfer-process-api")

// Connector
include(":launchers:connector")

0 comments on commit b69f6e4

Please sign in to comment.