From 753bf0953a9e9d1a3f3634a89acdcacb87cb18ea Mon Sep 17 00:00:00 2001 From: Julia Pampus Date: Thu, 20 Apr 2023 14:20:53 +0200 Subject: [PATCH] chore(dsp-negotiation-dispatcher): implement delegates --- ...DspNegotiationHttpDispatcherExtension.java | 22 +- .../ContractAgreementMessageHttpDelegate.java | 110 +++++++++ ...eementVerificationMessageHttpDelegate.java | 109 +++++++++ ...ctNegotiationEventMessageHttpDelegate.java | 108 +++++++++ ...tiationTerminationMessageHttpDelegate.java | 108 +++++++++ .../ContractRequestMessageHttpDelegate.java | 126 ++++++++++ ...tractAgreementMessageHttpDelegateTest.java | 149 ++++++++++++ ...ntVerificationMessageHttpDelegateTest.java | 121 ++++++++++ ...gotiationEventMessageHttpDelegateTest.java | 121 ++++++++++ ...ionTerminationMessageHttpDelegateTest.java | 121 ++++++++++ ...ontractRequestMessageHttpDelegateTest.java | 225 ++++++++++++++++++ 11 files changed, 1309 insertions(+), 11 deletions(-) create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegate.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegate.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegate.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegate.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegate.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegateTest.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegateTest.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegateTest.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegateTest.java create mode 100644 data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegateTest.java diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/DspNegotiationHttpDispatcherExtension.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/DspNegotiationHttpDispatcherExtension.java index 0255aa931ca..2ef668eb5be 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/DspNegotiationHttpDispatcherExtension.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/DspNegotiationHttpDispatcherExtension.java @@ -14,12 +14,12 @@ package org.eclipse.edc.protocol.dsp.negotiation.dispatcher; -import org.eclipse.edc.jsonld.transformer.JsonLdTransformerRegistry; -import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractAgreementMessageDelegate; -import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractAgreementVerificationMessageDelegate; -import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractNegotiationEventMessageDelegate; -import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractOfferRequestDelegate; -import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractRejectionHttpDelegate; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractAgreementMessageHttpDelegate; +import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractAgreementVerificationMessageHttpDelegate; +import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractNegotiationEventMessageHttpDelegate; +import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractNegotiationTerminationMessageHttpDelegate; +import org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate.ContractRequestMessageHttpDelegate; import org.eclipse.edc.protocol.dsp.spi.dispatcher.DspHttpRemoteMessageDispatcher; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -50,10 +50,10 @@ public String name() { public void initialize(ServiceExtensionContext context) { var objectMapper = typeManager.getMapper(TYPE_MANAGER_CONTEXT_JSON_LD); - messageDispatcher.registerDelegate(new ContractAgreementMessageDelegate(objectMapper, transformerRegistry)); - messageDispatcher.registerDelegate(new ContractAgreementVerificationMessageDelegate(objectMapper, transformerRegistry)); - messageDispatcher.registerDelegate(new ContractNegotiationEventMessageDelegate(objectMapper, transformerRegistry)); - messageDispatcher.registerDelegate(new ContractOfferRequestDelegate(objectMapper, transformerRegistry)); - messageDispatcher.registerDelegate(new ContractRejectionHttpDelegate(objectMapper, transformerRegistry)); + messageDispatcher.registerDelegate(new ContractAgreementMessageHttpDelegate(objectMapper, transformerRegistry)); + messageDispatcher.registerDelegate(new ContractAgreementVerificationMessageHttpDelegate(objectMapper, transformerRegistry)); + messageDispatcher.registerDelegate(new ContractNegotiationEventMessageHttpDelegate(objectMapper, transformerRegistry)); + messageDispatcher.registerDelegate(new ContractNegotiationTerminationMessageHttpDelegate(objectMapper, transformerRegistry)); + messageDispatcher.registerDelegate(new ContractRequestMessageHttpDelegate(objectMapper, transformerRegistry)); } } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegate.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegate.java new file mode 100644 index 00000000000..2a882e43ca3 --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegate.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.protocol.dsp.spi.dispatcher.DspHttpDispatcherDelegate; +import org.eclipse.edc.spi.EdcException; + +import java.util.function.Function; + +import static java.lang.String.format; +import static java.lang.String.join; +import static org.eclipse.edc.jsonld.spi.Namespaces.ODRL_PREFIX; +import static org.eclipse.edc.jsonld.spi.Namespaces.ODRL_SCHEMA; +import static org.eclipse.edc.jsonld.util.JsonLdUtil.compact; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.AGREEMENT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; + +/** + * Delegate for dispatching contract agreement message as defined in the dataspace protocol specification. + */ +public class ContractAgreementMessageHttpDelegate implements DspHttpDispatcherDelegate { + + private static final String APPLICATION_JSON = "application/json"; + + private final ObjectMapper mapper; + private final JsonLdTransformerRegistry transformerRegistry; + + public ContractAgreementMessageHttpDelegate(ObjectMapper mapper, JsonLdTransformerRegistry transformerRegistry) { + this.mapper = mapper; + this.transformerRegistry = transformerRegistry; + } + + @Override + public Class getMessageType() { + return ContractAgreementMessage.class; + } + + /** + * Sends a contract agreement message. The request body is constructed as defined in the dataspace + * protocol. The request is sent to the remote component using the path from the http binding. + * + * @param message the message. + * @return the built okhttp request. + */ + @Override + public Request buildRequest(ContractAgreementMessage message) { + var requestBody = RequestBody.create(toJson(message), MediaType.get(APPLICATION_JSON)); + return new Request.Builder() + .url(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + AGREEMENT) + .header("Content-Type", APPLICATION_JSON) + .post(requestBody) + .build(); + } + + /** + * Parses the response to an agreement message. The JSON-LD structure from the response body is + * expanded and returned. + * + * @return a function that contains the response body or null. + */ + @Override + public Function parseResponse() { + return response -> null; + } + + private String toJson(ContractAgreementMessage message) { + try { + var transformResult = transformerRegistry.transform(message, JsonObject.class); + if (transformResult.succeeded()) { + var compacted = compact(transformResult.getContent(), jsonLdContext()); + return mapper.writeValueAsString(compacted); + } + throw new EdcException(format("Failed to write request: %s", join(", ", transformResult.getFailureMessages()))); + } catch (JsonProcessingException e) { + throw new EdcException("Failed to serialize agreement message", e); + } + } + + // TODO refactor according to https://github.com/eclipse-edc/Connector/issues/2763 + private JsonObject jsonLdContext() { + return Json.createObjectBuilder() + .add(DSPACE_PREFIX, DSPACE_SCHEMA) + .add(ODRL_PREFIX, ODRL_SCHEMA) + .build(); + } +} diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegate.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegate.java new file mode 100644 index 00000000000..16af05d208b --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegate.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementVerificationMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.protocol.dsp.spi.dispatcher.DspHttpDispatcherDelegate; +import org.eclipse.edc.spi.EdcException; + +import java.util.function.Function; + +import static java.lang.String.format; +import static java.lang.String.join; +import static org.eclipse.edc.jsonld.util.JsonLdUtil.compact; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.AGREEMENT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.VERIFICATION; + +/** + * Delegate for dispatching contract agreement verification message as defined in the dataspace protocol specification. + */ +public class ContractAgreementVerificationMessageHttpDelegate implements DspHttpDispatcherDelegate { + + private static final String APPLICATION_JSON = "application/json"; + + private final ObjectMapper mapper; + private final JsonLdTransformerRegistry transformerRegistry; + + public ContractAgreementVerificationMessageHttpDelegate(ObjectMapper mapper, JsonLdTransformerRegistry transformerRegistry) { + this.mapper = mapper; + this.transformerRegistry = transformerRegistry; + } + + @Override + public Class getMessageType() { + return ContractAgreementVerificationMessage.class; + } + + /** + * Sends a contract agreement verification message. The request body is constructed as defined in + * the dataspace protocol. The request is sent to the remote component using the path from the http + * binding. + * + * @param message the message. + * @return the built okhttp request. + */ + @Override + public Request buildRequest(ContractAgreementVerificationMessage message) { + var requestBody = RequestBody.create(toJson(message), MediaType.get(APPLICATION_JSON)); + return new Request.Builder() + .url(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + AGREEMENT + VERIFICATION) + .header("Content-Type", APPLICATION_JSON) + .post(requestBody) + .build(); + } + + /** + * Parses the response to an agreement verification message. The JSON-LD structure from the response + * body is expanded and returned. + * + * @return a function that contains the response body or null. + */ + @Override + public Function parseResponse() { + return response -> null; + } + + private String toJson(ContractAgreementVerificationMessage message) { + try { + var transformResult = transformerRegistry.transform(message, JsonObject.class); + if (transformResult.succeeded()) { + var compacted = compact(transformResult.getContent(), jsonLdContext()); + return mapper.writeValueAsString(compacted); + } + throw new EdcException(format("Failed to write request: %s", join(", ", transformResult.getFailureMessages()))); + } catch (JsonProcessingException e) { + throw new EdcException("Failed to serialize agreement verification message", e); + } + } + + // TODO refactor according to https://github.com/eclipse-edc/Connector/issues/2763 + private JsonObject jsonLdContext() { + return Json.createObjectBuilder() + .add(DSPACE_PREFIX, DSPACE_SCHEMA) + .build(); + } +} diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegate.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegate.java new file mode 100644 index 00000000000..94b6e9493da --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegate.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractNegotiationEventMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.protocol.dsp.spi.dispatcher.DspHttpDispatcherDelegate; +import org.eclipse.edc.spi.EdcException; + +import java.util.function.Function; + +import static java.lang.String.format; +import static java.lang.String.join; +import static org.eclipse.edc.jsonld.util.JsonLdUtil.compact; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.EVENT; + +/** + * Delegate for dispatching contract negotiation event message as defined in the dataspace protocol specification. + */ +public class ContractNegotiationEventMessageHttpDelegate implements DspHttpDispatcherDelegate { + + private static final String APPLICATION_JSON = "application/json"; + + private final ObjectMapper mapper; + private final JsonLdTransformerRegistry transformerRegistry; + + public ContractNegotiationEventMessageHttpDelegate(ObjectMapper mapper, JsonLdTransformerRegistry transformerRegistry) { + this.mapper = mapper; + this.transformerRegistry = transformerRegistry; + } + + @Override + public Class getMessageType() { + return ContractNegotiationEventMessage.class; + } + + /** + * Sends a contract negotiation event message. The request body is constructed as defined in the + * dataspace protocol. The request is sent to the remote component using the path from the http + * binding. + * + * @param message the message. + * @return the built okhttp request. + */ + @Override + public Request buildRequest(ContractNegotiationEventMessage message) { + var requestBody = RequestBody.create(toJson(message), MediaType.get(APPLICATION_JSON)); + return new Request.Builder() + .url(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + EVENT) + .header("Content-Type", APPLICATION_JSON) + .post(requestBody) + .build(); + } + + /** + * Parses the response to a contract negotiation event message. The JSON-LD structure from the response + * body is expanded and returned. + * + * @return a function that contains the response body or null. + */ + @Override + public Function parseResponse() { + return response -> null; + } + + private String toJson(ContractNegotiationEventMessage message) { + try { + var transformResult = transformerRegistry.transform(message, JsonObject.class); + if (transformResult.succeeded()) { + var compacted = compact(transformResult.getContent(), jsonLdContext()); + return mapper.writeValueAsString(compacted); + } + throw new EdcException(format("Failed to write request: %s", join(", ", transformResult.getFailureMessages()))); + } catch (JsonProcessingException e) { + throw new EdcException("Failed to serialize negotiation event message", e); + } + } + + // TODO refactor according to https://github.com/eclipse-edc/Connector/issues/2763 + private JsonObject jsonLdContext() { + return Json.createObjectBuilder() + .add(DSPACE_PREFIX, DSPACE_SCHEMA) + .build(); + } +} diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegate.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegate.java new file mode 100644 index 00000000000..a44dc69b2db --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegate.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationTerminationMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.protocol.dsp.spi.dispatcher.DspHttpDispatcherDelegate; +import org.eclipse.edc.spi.EdcException; + +import java.util.function.Function; + +import static java.lang.String.format; +import static java.lang.String.join; +import static org.eclipse.edc.jsonld.util.JsonLdUtil.compact; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.TERMINATION; + +/** + * Delegate for dispatching contract negotiation termination message as defined in the dataspace protocol specification. + */ +public class ContractNegotiationTerminationMessageHttpDelegate implements DspHttpDispatcherDelegate { + + private static final String APPLICATION_JSON = "application/json"; + + private final ObjectMapper mapper; + private final JsonLdTransformerRegistry transformerRegistry; + + public ContractNegotiationTerminationMessageHttpDelegate(ObjectMapper mapper, JsonLdTransformerRegistry transformerRegistry) { + this.mapper = mapper; + this.transformerRegistry = transformerRegistry; + } + + @Override + public Class getMessageType() { + return ContractNegotiationTerminationMessage.class; + } + + /** + * Sends a contract negotiation termination message. The request body is constructed as defined + * in the dataspace protocol. The request is sent to the remote component using the path from the + * http binding. + * + * @param message the message. + * @return the built okhttp request. + */ + @Override + public Request buildRequest(ContractNegotiationTerminationMessage message) { + var requestBody = RequestBody.create(toJson(message), MediaType.get(APPLICATION_JSON)); + return new Request.Builder() + .url(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + TERMINATION) + .header("Content-Type", APPLICATION_JSON) + .post(requestBody) + .build(); + } + + /** + * Parses the response to a contract negotiation event message. The JSON-LD structure from the response + * body is expanded and returned. + * + * @return a function that contains the response body or null. + */ + @Override + public Function parseResponse() { + return response -> null; + } + + private String toJson(ContractNegotiationTerminationMessage message) { + try { + var transformResult = transformerRegistry.transform(message, JsonObject.class); + if (transformResult.succeeded()) { + var compacted = compact(transformResult.getContent(), jsonLdContext()); + return mapper.writeValueAsString(compacted); + } + throw new EdcException(format("Failed to write request: %s", join(", ", transformResult.getFailureMessages()))); + } catch (JsonProcessingException e) { + throw new EdcException("Failed to serialize negotiation termination message", e); + } + } + + // TODO refactor according to https://github.com/eclipse-edc/Connector/issues/2763 + private JsonObject jsonLdContext() { + return Json.createObjectBuilder() + .add(DSPACE_PREFIX, DSPACE_SCHEMA) + .build(); + } +} diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegate.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegate.java new file mode 100644 index 00000000000..0f6b4ee8c81 --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegate.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequestMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.protocol.dsp.spi.dispatcher.DspHttpDispatcherDelegate; +import org.eclipse.edc.spi.EdcException; + +import java.io.IOException; +import java.util.function.Function; + +import static java.lang.String.format; +import static java.lang.String.join; +import static org.eclipse.edc.jsonld.spi.Namespaces.ODRL_PREFIX; +import static org.eclipse.edc.jsonld.spi.Namespaces.ODRL_SCHEMA; +import static org.eclipse.edc.jsonld.util.JsonLdUtil.compact; +import static org.eclipse.edc.jsonld.util.JsonLdUtil.expand; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.CONTRACT_REQUEST; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.INITIAL_CONTRACT_REQUEST; + +/** + * Delegate for dispatching contract request message as defined in the dataspace protocol specification. + */ +public class ContractRequestMessageHttpDelegate implements DspHttpDispatcherDelegate { + + private static final String APPLICATION_JSON = "application/json"; + + private final ObjectMapper mapper; + private final JsonLdTransformerRegistry transformerRegistry; + + public ContractRequestMessageHttpDelegate(ObjectMapper mapper, JsonLdTransformerRegistry transformerRegistry) { + this.mapper = mapper; + this.transformerRegistry = transformerRegistry; + } + + @Override + public Class getMessageType() { + return ContractRequestMessage.class; + } + + /** + * Sends a contract request message. The request body is constructed as defined in the dataspace + * protocol. The request is sent to the remote component using the path from the http binding. + * + * @param message the message. + * @return the built okhttp request. + */ + @Override + public Request buildRequest(ContractRequestMessage message) { + var requestBody = RequestBody.create(toJson(message), MediaType.get(APPLICATION_JSON)); + if (message.getType() == ContractRequestMessage.Type.INITIAL) { + return new Request.Builder() + .url(message.getCallbackAddress() + BASE_PATH + INITIAL_CONTRACT_REQUEST) + .header("Content-Type", "application/json") + .post(requestBody) + .build(); + } else { + return new Request.Builder() + .url(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + CONTRACT_REQUEST) + .header("Content-Type", "application/json") + .post(requestBody) + .build(); + } + } + + @Override + public Function parseResponse() { + return response -> { + try { + var jsonObject = mapper.readValue(response.body().bytes(), JsonObject.class); + return expand(jsonObject).get(0); + } catch (NullPointerException e) { + throw new EdcException("Failed to read response body, as body was null."); + } catch (IndexOutOfBoundsException e) { + throw new EdcException("Failed to expand JSON-LD in response body.", e); + } catch (IOException e) { + throw new EdcException("Failed to read response body.", e); + } + }; + } + + private String toJson(ContractRequestMessage message) { + try { + var transformResult = transformerRegistry.transform(message, JsonObject.class); + if (transformResult.succeeded()) { + var compacted = compact(transformResult.getContent(), jsonLdContext()); + return mapper.writeValueAsString(compacted); + } + throw new EdcException(format("Failed to write request: %s", join(", ", transformResult.getFailureMessages()))); + } catch (JsonProcessingException e) { + throw new EdcException("Failed to serialize contract request message", e); + } + } + + // TODO refactor according to https://github.com/eclipse-edc/Connector/issues/2763 + private JsonObject jsonLdContext() { + return Json.createObjectBuilder() + .add(DSPACE_PREFIX, DSPACE_SCHEMA) + .add(ODRL_PREFIX, ODRL_SCHEMA) + .build(); + } +} diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegateTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegateTest.java new file mode 100644 index 00000000000..f5bce0bc02b --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementMessageHttpDelegateTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.Request; +import okhttp3.Response; +import okio.Buffer; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.policy.model.Action; +import org.eclipse.edc.policy.model.Duty; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.Prohibition; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.AGREEMENT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ContractAgreementMessageHttpDelegateTest { + + private final ObjectMapper mapper = mock(ObjectMapper.class); + private final JsonLdTransformerRegistry registry = mock(JsonLdTransformerRegistry.class); + + private ContractAgreementMessageHttpDelegate delegate; + + @BeforeEach + void setUp() { + delegate = new ContractAgreementMessageHttpDelegate(mapper, registry); + } + + @Test + void getMessageType() { + assertThat(delegate.getMessageType()).isEqualTo(ContractAgreementMessage.class); + } + + @Test + void buildRequest() throws IOException { + var jsonObject = Json.createObjectBuilder().add(DSPACE_SCHEMA + "key", "value").build(); + var serializedBody = "message"; + + when(registry.transform(isA(ContractAgreementMessage.class), eq(JsonObject.class))).thenReturn(Result.success(jsonObject)); + when(mapper.writeValueAsString(any(JsonObject.class))).thenReturn(serializedBody); + + var message = message(); + var httpRequest = delegate.buildRequest(message); + + assertThat(httpRequest.url().url()).hasToString(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + AGREEMENT); + assertThat(readRequestBody(httpRequest)).isEqualTo(serializedBody); + + verify(registry, times(1)).transform(any(ContractAgreementMessage.class), eq(JsonObject.class)); + verify(mapper, times(1)).writeValueAsString(argThat(json -> ((JsonObject) json).get(CONTEXT) != null && ((JsonObject) json).get(DSPACE_PREFIX + ":key") != null)); + } + + @Test + void buildRequest_transformationFails_throwException() { + when(registry.transform(any(ContractAgreementMessage.class), eq(JsonObject.class))).thenReturn(Result.failure("error")); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void buildRequest_writingJsonFails_throwException() throws JsonProcessingException { + when(registry.transform(any(ContractAgreementMessage.class), eq(JsonObject.class))).thenReturn(Result.success(Json.createObjectBuilder().build())); + when(mapper.writeValueAsString(any(JsonObject.class))).thenThrow(JsonProcessingException.class); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void parseResponse_returnNull() { + var response = mock(Response.class); + + assertThat(delegate.parseResponse().apply(response)).isNull(); + } + + private ContractAgreementMessage message() { + var value = "example"; + return ContractAgreementMessage.Builder.newInstance() + .protocol(value) + .processId(value) + .callbackAddress("http://connector") + .contractAgreement(contractAgreement()) + .build(); + } + + private ContractAgreement contractAgreement() { + return ContractAgreement.Builder.newInstance() + .id(String.valueOf(UUID.randomUUID())) + .providerAgentId("agentId") + .consumerAgentId("agentId") + .assetId("assetId") + .policy(policy()).build(); + } + + private Policy policy() { + var action = Action.Builder.newInstance().type("USE").build(); + var permission = Permission.Builder.newInstance().action(action).build(); + var prohibition = Prohibition.Builder.newInstance().action(action).build(); + var duty = Duty.Builder.newInstance().action(action).build(); + return Policy.Builder.newInstance() + .permission(permission) + .prohibition(prohibition) + .duty(duty) + .build(); + } + + private String readRequestBody(Request request) throws IOException { + var buffer = new Buffer(); + request.body().writeTo(buffer); + return buffer.readUtf8(); + } +} \ No newline at end of file diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegateTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegateTest.java new file mode 100644 index 00000000000..2c17e3217b0 --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractAgreementVerificationMessageHttpDelegateTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.Request; +import okhttp3.Response; +import okio.Buffer; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementVerificationMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.AGREEMENT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.VERIFICATION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ContractAgreementVerificationMessageHttpDelegateTest { + + private final ObjectMapper mapper = mock(ObjectMapper.class); + private final JsonLdTransformerRegistry registry = mock(JsonLdTransformerRegistry.class); + + private ContractAgreementVerificationMessageHttpDelegate delegate; + + @BeforeEach + void setUp() { + delegate = new ContractAgreementVerificationMessageHttpDelegate(mapper, registry); + } + + @Test + void getMessageType() { + assertThat(delegate.getMessageType()).isEqualTo(ContractAgreementVerificationMessage.class); + } + + @Test + void buildRequest() throws IOException { + var jsonObject = Json.createObjectBuilder().add(DSPACE_SCHEMA + "key", "value").build(); + var serializedBody = "message"; + + when(registry.transform(isA(ContractAgreementVerificationMessage.class), eq(JsonObject.class))).thenReturn(Result.success(jsonObject)); + when(mapper.writeValueAsString(any(JsonObject.class))).thenReturn(serializedBody); + + var message = message(); + var httpRequest = delegate.buildRequest(message); + + assertThat(httpRequest.url().url()).hasToString(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + AGREEMENT + VERIFICATION); + assertThat(readRequestBody(httpRequest)).isEqualTo(serializedBody); + + verify(registry, times(1)).transform(any(ContractAgreementVerificationMessage.class), eq(JsonObject.class)); + verify(mapper, times(1)).writeValueAsString(argThat(json -> ((JsonObject) json).get(CONTEXT) != null && ((JsonObject) json).get(DSPACE_PREFIX + ":key") != null)); + } + + @Test + void buildRequest_transformationFails_throwException() { + when(registry.transform(any(ContractAgreementVerificationMessage.class), eq(JsonObject.class))).thenReturn(Result.failure("error")); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void buildRequest_writingJsonFails_throwException() throws JsonProcessingException { + when(registry.transform(any(ContractAgreementVerificationMessage.class), eq(JsonObject.class))).thenReturn(Result.success(Json.createObjectBuilder().build())); + when(mapper.writeValueAsString(any(JsonObject.class))).thenThrow(JsonProcessingException.class); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void parseResponse_returnNull() { + var response = mock(Response.class); + + assertThat(delegate.parseResponse().apply(response)).isNull(); + } + + private ContractAgreementVerificationMessage message() { + var value = "example"; + return ContractAgreementVerificationMessage.Builder.newInstance() + .protocol(value) + .processId(value) + .callbackAddress("http://connector") + .build(); + } + + private String readRequestBody(Request request) throws IOException { + var buffer = new Buffer(); + request.body().writeTo(buffer); + return buffer.readUtf8(); + } +} \ No newline at end of file diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegateTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegateTest.java new file mode 100644 index 00000000000..3b57289eadd --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationEventMessageHttpDelegateTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.Request; +import okhttp3.Response; +import okio.Buffer; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractNegotiationEventMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.EVENT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ContractNegotiationEventMessageHttpDelegateTest { + + private final ObjectMapper mapper = mock(ObjectMapper.class); + private final JsonLdTransformerRegistry registry = mock(JsonLdTransformerRegistry.class); + + private ContractNegotiationEventMessageHttpDelegate delegate; + + @BeforeEach + void setUp() { + delegate = new ContractNegotiationEventMessageHttpDelegate(mapper, registry); + } + + @Test + void getMessageType() { + assertThat(delegate.getMessageType()).isEqualTo(ContractNegotiationEventMessage.class); + } + + @Test + void buildRequest() throws IOException { + var jsonObject = Json.createObjectBuilder().add(DSPACE_SCHEMA + "key", "value").build(); + var serializedBody = "message"; + + when(registry.transform(isA(ContractNegotiationEventMessage.class), eq(JsonObject.class))).thenReturn(Result.success(jsonObject)); + when(mapper.writeValueAsString(any(JsonObject.class))).thenReturn(serializedBody); + + var message = message(); + var httpRequest = delegate.buildRequest(message); + + assertThat(httpRequest.url().url()).hasToString(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + EVENT); + assertThat(readRequestBody(httpRequest)).isEqualTo(serializedBody); + + verify(registry, times(1)).transform(any(ContractNegotiationEventMessage.class), eq(JsonObject.class)); + verify(mapper, times(1)).writeValueAsString(argThat(json -> ((JsonObject) json).get(CONTEXT) != null && ((JsonObject) json).get(DSPACE_PREFIX + ":key") != null)); + } + + @Test + void buildRequest_transformationFails_throwException() { + when(registry.transform(any(ContractNegotiationEventMessage.class), eq(JsonObject.class))).thenReturn(Result.failure("error")); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void buildRequest_writingJsonFails_throwException() throws JsonProcessingException { + when(registry.transform(any(ContractNegotiationEventMessage.class), eq(JsonObject.class))).thenReturn(Result.success(Json.createObjectBuilder().build())); + when(mapper.writeValueAsString(any(JsonObject.class))).thenThrow(JsonProcessingException.class); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void parseResponse_returnNull() { + var response = mock(Response.class); + + assertThat(delegate.parseResponse().apply(response)).isNull(); + } + + private ContractNegotiationEventMessage message() { + var value = "example"; + return ContractNegotiationEventMessage.Builder.newInstance() + .protocol(value) + .processId(value) + .callbackAddress("http://connector") + .type(ContractNegotiationEventMessage.Type.FINALIZED) + .build(); + } + + private String readRequestBody(Request request) throws IOException { + var buffer = new Buffer(); + request.body().writeTo(buffer); + return buffer.readUtf8(); + } +} \ No newline at end of file diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegateTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegateTest.java new file mode 100644 index 00000000000..c41b5eec48d --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractNegotiationTerminationMessageHttpDelegateTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.Request; +import okhttp3.Response; +import okio.Buffer; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationTerminationMessage; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.TERMINATION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ContractNegotiationTerminationMessageHttpDelegateTest { + + private final ObjectMapper mapper = mock(ObjectMapper.class); + private final JsonLdTransformerRegistry registry = mock(JsonLdTransformerRegistry.class); + + private ContractNegotiationTerminationMessageHttpDelegate delegate; + + @BeforeEach + void setUp() { + delegate = new ContractNegotiationTerminationMessageHttpDelegate(mapper, registry); + } + + @Test + void getMessageType() { + assertThat(delegate.getMessageType()).isEqualTo(ContractNegotiationTerminationMessage.class); + } + + @Test + void buildRequest() throws IOException { + var jsonObject = Json.createObjectBuilder().add(DSPACE_SCHEMA + "key", "value").build(); + var serializedBody = "message"; + + when(registry.transform(isA(ContractNegotiationTerminationMessage.class), eq(JsonObject.class))).thenReturn(Result.success(jsonObject)); + when(mapper.writeValueAsString(any(JsonObject.class))).thenReturn(serializedBody); + + var message = message(); + var httpRequest = delegate.buildRequest(message); + + assertThat(httpRequest.url().url()).hasToString(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + TERMINATION); + assertThat(readRequestBody(httpRequest)).isEqualTo(serializedBody); + + verify(registry, times(1)).transform(any(ContractNegotiationTerminationMessage.class), eq(JsonObject.class)); + verify(mapper, times(1)).writeValueAsString(argThat(json -> ((JsonObject) json).get(CONTEXT) != null && ((JsonObject) json).get(DSPACE_PREFIX + ":key") != null)); + } + + @Test + void buildRequest_transformationFails_throwException() { + when(registry.transform(any(ContractNegotiationTerminationMessage.class), eq(JsonObject.class))).thenReturn(Result.failure("error")); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void buildRequest_writingJsonFails_throwException() throws JsonProcessingException { + when(registry.transform(any(ContractNegotiationTerminationMessage.class), eq(JsonObject.class))).thenReturn(Result.success(Json.createObjectBuilder().build())); + when(mapper.writeValueAsString(any(JsonObject.class))).thenThrow(JsonProcessingException.class); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void parseResponse_returnNull() { + var response = mock(Response.class); + + assertThat(delegate.parseResponse().apply(response)).isNull(); + } + + private ContractNegotiationTerminationMessage message() { + var value = "example"; + return ContractNegotiationTerminationMessage.Builder.newInstance() + .protocol(value) + .processId(value) + .callbackAddress("http://connector") + .rejectionReason(value) + .build(); + } + + private String readRequestBody(Request request) throws IOException { + var buffer = new Buffer(); + request.body().writeTo(buffer); + return buffer.readUtf8(); + } +} \ No newline at end of file diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegateTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegateTest.java new file mode 100644 index 00000000000..48c2e9ecb87 --- /dev/null +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-http-dispatcher/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/dispatcher/delegate/ContractRequestMessageHttpDelegateTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2023 Fraunhofer Institute for Software and Systems Engineering + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Fraunhofer Institute for Software and Systems Engineering - initial API and implementation + * + */ + +package org.eclipse.edc.protocol.dsp.negotiation.dispatcher.delegate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequestMessage; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.jsonld.spi.JsonLdKeywords; +import org.eclipse.edc.jsonld.spi.transformer.JsonLdTransformerRegistry; +import org.eclipse.edc.policy.model.Action; +import org.eclipse.edc.policy.model.Duty; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.Prohibition; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.asset.Asset; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.util.JsonLdUtil.expand; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_CONTRACT_NEGOTIATION; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_CHECKSUM; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_STATE; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_STATE_REQUESTED; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_PREFIX; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.DspNegotiationPropertyAndTypeNames.DSPACE_SCHEMA; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.CONTRACT_REQUEST; +import static org.eclipse.edc.protocol.dsp.negotiation.spi.NegotiationApiPaths.INITIAL_CONTRACT_REQUEST; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ContractRequestMessageHttpDelegateTest { + + private final ObjectMapper mapper = mock(ObjectMapper.class); + private final JsonLdTransformerRegistry registry = mock(JsonLdTransformerRegistry.class); + + private ContractRequestMessageHttpDelegate delegate; + + @BeforeEach + void setUp() { + delegate = new ContractRequestMessageHttpDelegate(mapper, registry); + } + + @Test + void getMessageType() { + assertThat(delegate.getMessageType()).isEqualTo(ContractRequestMessage.class); + } + + @Test + void buildRequest_initial() throws IOException { + var jsonObject = Json.createObjectBuilder().add(DSPACE_SCHEMA + "key", "value").build(); + var serializedBody = "message"; + + when(registry.transform(isA(ContractRequestMessage.class), eq(JsonObject.class))).thenReturn(Result.success(jsonObject)); + when(mapper.writeValueAsString(any(JsonObject.class))).thenReturn(serializedBody); + + var message = message_initial(); + var httpRequest = delegate.buildRequest(message); + + assertThat(httpRequest.url().url()).hasToString(message.getCallbackAddress() + BASE_PATH + INITIAL_CONTRACT_REQUEST); + assertThat(readRequestBody(httpRequest)).isEqualTo(serializedBody); + + verify(registry, times(1)).transform(any(ContractRequestMessage.class), eq(JsonObject.class)); + verify(mapper, times(1)).writeValueAsString(argThat(json -> ((JsonObject) json).get(CONTEXT) != null && ((JsonObject) json).get(DSPACE_PREFIX + ":key") != null)); + } + + @Test + void buildRequest() throws IOException { + var jsonObject = Json.createObjectBuilder().add(DSPACE_SCHEMA + "key", "value").build(); + var serializedBody = "message"; + + when(registry.transform(isA(ContractRequestMessage.class), eq(JsonObject.class))).thenReturn(Result.success(jsonObject)); + when(mapper.writeValueAsString(any(JsonObject.class))).thenReturn(serializedBody); + + var message = message(); + var httpRequest = delegate.buildRequest(message); + + assertThat(httpRequest.url().url()).hasToString(message.getCallbackAddress() + BASE_PATH + message.getProcessId() + CONTRACT_REQUEST); + assertThat(readRequestBody(httpRequest)).isEqualTo(serializedBody); + + verify(registry, times(1)).transform(any(ContractRequestMessage.class), eq(JsonObject.class)); + verify(mapper, times(1)).writeValueAsString(argThat(json -> ((JsonObject) json).get(CONTEXT) != null && ((JsonObject) json).get(DSPACE_PREFIX + ":key") != null)); + } + + @Test + void buildRequest_transformationFails_throwException() { + when(registry.transform(any(ContractRequestMessage.class), eq(JsonObject.class))).thenReturn(Result.failure("error")); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void buildRequest_writingJsonFails_throwException() throws JsonProcessingException { + when(registry.transform(any(ContractRequestMessage.class), eq(JsonObject.class))).thenReturn(Result.success(Json.createObjectBuilder().build())); + when(mapper.writeValueAsString(any(JsonObject.class))).thenThrow(JsonProcessingException.class); + + assertThatThrownBy(() -> delegate.buildRequest(message())).isInstanceOf(EdcException.class); + } + + @Test + void parseResponse_returnNegotiation() throws IOException { + var response = mock(Response.class); + var responseBody = mock(ResponseBody.class); + var bytes = "test".getBytes(); + + var expanded = expand(negotiation()).get(0); + + when(response.body()).thenReturn(responseBody); + when(responseBody.bytes()).thenReturn(bytes); + when(mapper.readValue(bytes, JsonObject.class)).thenReturn(negotiation()); + + var result = delegate.parseResponse().apply(response); + + assertThat(result).isEqualTo(expanded); + verify(mapper, times(1)).readValue(bytes, JsonObject.class); + } + + @Test + void parseResponse_responseBodyNull_throwException() { + var response = mock(Response.class); + + when(response.body()).thenReturn(null); + + assertThatThrownBy(() -> delegate.parseResponse().apply(response)).isInstanceOf(EdcException.class); + } + + private ContractRequestMessage message() { + var value = "example"; + return ContractRequestMessage.Builder.newInstance() + .protocol(value) + .processId(value) + .callbackAddress("http://connector") + .dataSet(value) + .contractOffer(contractOffer()) + .type(ContractRequestMessage.Type.COUNTER_OFFER) + .build(); + } + + private ContractRequestMessage message_initial() { + var value = "example"; + return ContractRequestMessage.Builder.newInstance() + .protocol(value) + .processId(value) + .callbackAddress("http://connector") + .dataSet(value) + .contractOffer(contractOffer()) + .type(ContractRequestMessage.Type.INITIAL) + .build(); + } + + private ContractOffer contractOffer() { + return ContractOffer.Builder.newInstance() + .id(String.valueOf(UUID.randomUUID())) + .asset(Asset.Builder.newInstance().id("assetId").build()) + .contractStart(ZonedDateTime.now()) + .contractEnd(ZonedDateTime.now()) + .policy(policy()).build(); + } + + private Policy policy() { + var action = Action.Builder.newInstance().type("USE").build(); + var permission = Permission.Builder.newInstance().action(action).build(); + var prohibition = Prohibition.Builder.newInstance().action(action).build(); + var duty = Duty.Builder.newInstance().action(action).build(); + return Policy.Builder.newInstance() + .permission(permission) + .prohibition(prohibition) + .duty(duty) + .build(); + } + + private JsonObject negotiation() { + var value = "example"; + var builder = Json.createObjectBuilder(); + builder.add(JsonLdKeywords.ID, value); + builder.add(JsonLdKeywords.TYPE, DSPACE_CONTRACT_NEGOTIATION); + + builder.add(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID, value); + builder.add(DSPACE_NEGOTIATION_PROPERTY_STATE, DSPACE_NEGOTIATION_STATE_REQUESTED); + builder.add(DSPACE_NEGOTIATION_PROPERTY_CHECKSUM, value); + + return builder.build(); + } + + private String readRequestBody(Request request) throws IOException { + var buffer = new Buffer(); + request.body().writeTo(buffer); + return buffer.readUtf8(); + } +} \ No newline at end of file