Skip to content

feat: make data address handle complex properties #5012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public List<ServiceProvider> getServiceProviders() {
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"injectionTarget=" + injectionTarget +
'}';
"injectionTarget=" + injectionTarget +
'}';
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.transform.spi.TransformerContext;
import org.jetbrains.annotations.NotNull;
Expand All @@ -28,10 +29,14 @@
public class JsonObjectFromDataAddressTransformer extends AbstractJsonLdTransformer<DataAddress, JsonObject> {

private final JsonBuilderFactory jsonBuilderFactory;
private final TypeManager typeManager;
private final String typeContext;

public JsonObjectFromDataAddressTransformer(JsonBuilderFactory jsonBuilderFactory) {
public JsonObjectFromDataAddressTransformer(JsonBuilderFactory jsonBuilderFactory, TypeManager mapper, String typeContext) {
super(DataAddress.class, JsonObject.class);
this.jsonBuilderFactory = jsonBuilderFactory;
this.typeManager = mapper;
this.typeContext = typeContext;
}

@Override
Expand All @@ -40,7 +45,7 @@ public JsonObjectFromDataAddressTransformer(JsonBuilderFactory jsonBuilderFactor

builder.add(TYPE, EDC_NAMESPACE + "DataAddress");

dataAddress.getProperties().forEach((key, value) -> builder.add(key, (String) value)); // TODO: handle different types
transformProperties(dataAddress.getProperties(), builder, typeManager.getMapper(typeContext), context);

return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

package org.eclipse.edc.transform.transformer.edc.to;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer;
Expand All @@ -23,7 +22,6 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static jakarta.json.JsonValue.ValueType.STRING;
import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE;


Expand All @@ -47,23 +45,15 @@ public JsonObjectToDataAddressTransformer() {
}

private void transformProperties(String key, JsonValue jsonValue, DataAddress.Builder builder, TransformerContext context) {
if ((PROPERTIES_KEY).equals(key) && jsonValue instanceof JsonArray) {
var props = jsonValue.asJsonArray().getJsonObject(0);
visitProperties(props, (k, val) -> transformProperties(k, val, builder, context));
var firstValue = returnJsonObject(jsonValue, context, key, true);

if (firstValue != null && key.equals(PROPERTIES_KEY)) {
visitProperties(firstValue, (k, val) -> transformProperties(k, val, builder, context));
} else {
var object = transformGenericProperty(jsonValue, context);
if (object instanceof String) {
builder.property(key, object.toString());
} else {
context.problem()
.unexpectedType()
.type("property")
.property(key)
.actual(object == null ? "null" : object.toString())
.expected(STRING)
.report();
}
builder.property(key, transformGenericProperty(jsonValue, context));
}

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonValue;
import org.eclipse.edc.jsonld.TitaniumJsonLd;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
Expand All @@ -28,20 +27,29 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

import static jakarta.json.Json.createArrayBuilder;
import static jakarta.json.Json.createObjectBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.LIST;
import static org.assertj.core.api.InstanceOfAssertFactories.MAP;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB;
import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE;
import static org.eclipse.edc.spi.constants.CoreConstants.EDC_PREFIX;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


class JsonObjectToDataAddressTransformerTest {
private static final int CUSTOM_PAYLOAD_AGE = 34;
private static final String CUSTOM_PAYLOAD_NAME = "max";
Expand Down Expand Up @@ -87,33 +95,90 @@ void transform_withCustomProps() {
assertThat(dataAddress).isNotNull();
assertThat(dataAddress.getType()).isEqualTo(TEST_TYPE);
assertThat(dataAddress.getStringProperty(EDC_NAMESPACE + "my-test-prop")).isEqualTo("some-test-value");
assertThat(dataAddress.getProperties()).hasSize(3);
}

@Test
void transform_withComplexCustomProps_shouldReportProblem() {
when(transformerContext.transform(isA(JsonValue.class), eq(Payload.class))).thenReturn(new Payload(CUSTOM_PAYLOAD_NAME, CUSTOM_PAYLOAD_AGE));
void transform_withNestedJson() {
var json = createDataAddress()
.add(EDC_NAMESPACE + "properties", createObjectBuilder()
.add("payload", createPayloadBuilder().build())
.add("payload", createObjectBuilder()
.add("sabesxopaulovem", createPayloadBuilder().build())
.add("arrayInPayload", createArrayBuilder().add("innerValue1").add("innerValue2").build()).build())
.build())
.build();

var dataAddress = transformer.transform(expand(json), transformerContext);

assertThat(dataAddress).isNotNull();
assertThat(dataAddress.getType()).isEqualTo(TEST_TYPE);
assertThat(dataAddress.getKeyName()).isEqualTo(TEST_KEY_NAME);
assertThat(dataAddress.getProperties()).hasSize(2);
assertThat(dataAddress.getProperty(EDC_NAMESPACE + "payload"))
.isNotNull()
.asInstanceOf(MAP)
.hasSize(2)
.hasEntrySatisfying(EDC_NAMESPACE + "sabesxopaulovem",
v -> assertThat(v).asInstanceOf(LIST).first().asInstanceOf(MAP)
.hasSize(3)
.containsEntry(EDC_NAMESPACE + "name", List.of(Map.of(VALUE, CUSTOM_PAYLOAD_NAME)))
.containsEntry(EDC_NAMESPACE + "age", List.of(Map.of(VALUE, CUSTOM_PAYLOAD_AGE))))
.hasEntrySatisfying(EDC_NAMESPACE + "arrayInPayload",
v -> assertThat(v).asInstanceOf(LIST)
.hasSize(2)
.containsExactly(Map.of(VALUE, "innerValue1"), Map.of(VALUE, "innerValue2")));
verify(transformerContext, never()).reportProblem(any());
}

@Test
void transform_withArrayProperty() {
var json = createDataAddress()
.add(EDC_NAMESPACE + "properties", createObjectBuilder()
.add("array", createArrayBuilder().add("string1").add("string2").build())
.build())
.build();

verify(transformerContext).reportProblem(any());
var dataAddress = transformer.transform(expand(json), transformerContext);

assertThat(dataAddress).isNotNull();
assertThat(dataAddress.getProperty(EDC_NAMESPACE + "array"))
.isNotNull()
.isEqualTo(List.of("string1", "string2"));
verify(transformerContext, never()).reportProblem(any());
}

@Test
void transform_withArrayOfObjects() {
var json = createDataAddress()
.add(EDC_NAMESPACE + "properties", createObjectBuilder()
.add("arrayOfObjects", createArrayBuilder()
.add(createObjectBuilder().add("name", CUSTOM_PAYLOAD_NAME).build())
.add(createObjectBuilder().add("age", CUSTOM_PAYLOAD_AGE).build())
.build())
.build())
.build();

var dataAddress = transformer.transform(expand(json), transformerContext);

assertThat(dataAddress).isNotNull();
assertThat(dataAddress.getProperty(EDC_NAMESPACE + "arrayOfObjects"))
.isNotNull()
.asInstanceOf(LIST)
.hasSize(2)
.satisfies(list -> {
assertThat(list.get(0)).asInstanceOf(MAP)
.hasSize(1)
.containsEntry(EDC_NAMESPACE + "name", List.of(Map.of(VALUE, CUSTOM_PAYLOAD_NAME)));
assertThat(list.get(1)).asInstanceOf(MAP)
.hasSize(1)
.containsEntry(EDC_NAMESPACE + "age", List.of(Map.of(VALUE, CUSTOM_PAYLOAD_AGE)));
});
verify(transformerContext, never()).reportProblem(any());
}

private JsonObjectBuilder createDataAddress() {
return createObjectBuilder()
.add(CONTEXT, createContextBuilder().build())
.add(TYPE, EDC_NAMESPACE + "DataAddress")
.add(EDC_NAMESPACE + DataAddress.EDC_DATA_ADDRESS_TYPE_PROPERTY, TEST_TYPE)
.add(EDC_NAMESPACE + DataAddress.EDC_DATA_ADDRESS_KEY_NAME, TEST_KEY_NAME);
.add(DataAddress.EDC_DATA_ADDRESS_TYPE_PROPERTY, TEST_TYPE)
.add(DataAddress.EDC_DATA_ADDRESS_KEY_NAME, TEST_KEY_NAME);
}

private JsonObjectBuilder createContextBuilder() {
Expand All @@ -132,7 +197,4 @@ private JsonObjectBuilder createPayloadBuilder() {
private JsonObject expand(JsonObject jsonObject) {
return jsonLd.expand(jsonObject).orElseThrow(f -> new AssertionError(f.getFailureDetail()));
}

private record Payload(String name, int age) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,33 @@
package org.eclipse.edc.protocol.dsp.transferprocess.transform.from;

import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObjectBuilder;
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.transform.spi.ProblemBuilder;
import org.eclipse.edc.transform.spi.TransformerContext;
import org.eclipse.edc.transform.transformer.edc.from.JsonObjectFromDataAddressTransformer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE;
import static org.eclipse.edc.spi.types.domain.DataAddress.EDC_DATA_ADDRESS_KEY_NAME;
import static org.eclipse.edc.spi.types.domain.DataAddress.EDC_DATA_ADDRESS_TYPE_PROPERTY;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


class JsonObjectFromDataAddressTransformerTest {
Expand All @@ -38,32 +51,93 @@ class JsonObjectFromDataAddressTransformerTest {
private final String key = "testKey";
private final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Map.of());
private final TransformerContext context = mock(TransformerContext.class);
private final TypeManager typeManager = mock();

private JsonObjectFromDataAddressTransformer transformer;

@BeforeEach
void setUp() {
transformer = new JsonObjectFromDataAddressTransformer(jsonFactory);
transformer = new JsonObjectFromDataAddressTransformer(jsonFactory, typeManager, "test");
when(typeManager.getMapper("test")).thenReturn(JacksonJsonLd.createObjectMapper());
}

@Test
void transform() {
var innerDataAddress = DataAddress.Builder.newInstance()
.property(EDC_DATA_ADDRESS_TYPE_PROPERTY, "type")
.build();
var message = DataAddress.Builder.newInstance()
.type(type)
.keyName(key)
.property(TEST_KEY, TEST_VALUE)
.property("string", "test")
.property("integer", 123)
.property("whatAboutDouble", 123.456)
.property("nestedDataAddress", innerDataAddress)
.property("nestedJsonObject", Map.of("key", Map.of("testKey", "testValue")))
.property("jsonArray", List.of("string1", "string2"))
.property("arrayOfObjects", List.of(
Map.of("key1", "value1"),
Map.of("key2", "value2")))
.property("nestedJsonObjectWithArray", Map.of(
"key", List.of("value1", "value2"),
"anotherKey", List.of("value3", "value4")))
.build();

var result = transformer.transform(message, context);
var expectedJson = jsonObject()
.add(TYPE, EDC_NAMESPACE + "DataAddress")
.add(EDC_DATA_ADDRESS_TYPE_PROPERTY, type)
.add(EDC_DATA_ADDRESS_KEY_NAME, key)
.add("string", "test")
.add("integer", 123)
.add("whatAboutDouble", 123.456)
.add("nestedDataAddress",
jsonObject()
.add("properties", jsonObject().add(EDC_DATA_ADDRESS_TYPE_PROPERTY, "type")))
.add("nestedJsonObject",
jsonObject()
.add("key", jsonObject().add("testKey", "testValue")))
.add("jsonArray",
jsonArray()
.add("string1").add("string2"))
.add("arrayOfObjects",
jsonArray()
.add(jsonObject().add("key1", "value1"))
.add(jsonObject().add("key2", "value2")))
.add("nestedJsonObjectWithArray",
jsonObject()
.add("key", jsonArray().add("value1").add("value2"))
.add("anotherKey", jsonArray().add("value3").add("value4")))
.build();

assertThat(result).isNotNull();
assertThat(result.getJsonString(TEST_KEY).getString()).isEqualTo(TEST_VALUE);
assertThat(result.getJsonString(DataAddress.EDC_DATA_ADDRESS_TYPE_PROPERTY).getString()).isEqualTo(type);
assertThat(result.getJsonString(DataAddress.EDC_DATA_ADDRESS_KEY_NAME).getString()).isEqualTo(key);
var result = transformer.transform(message, context);

assertThat(result).usingRecursiveAssertion().isEqualTo(expectedJson);
verify(context, never()).reportProblem(anyString());
}

@Test
void transform_DataAddressWithCyclicReference() {
when(context.problem()).thenReturn(new ProblemBuilder(context));
var innerDataAddress = new LinkedHashMap<String, Object>();
innerDataAddress.put(EDC_DATA_ADDRESS_TYPE_PROPERTY, "type");

var rootDataAddress = DataAddress.Builder.newInstance()
.type(type)
.keyName(key)
.property("nestedDataAddress", innerDataAddress)
.build();

innerDataAddress.put("cycle", rootDataAddress);

var result = transformer.transform(rootDataAddress, context);

assertThat(result)
.isNotNull()
.doesNotContainKey("nestedDataAddress");

verify(context).reportProblem(contains("Infinite recursion (StackOverflowError)"));
}

@Test
void transform_withNamespace() {
var schema = "https://some.custom.org/schema/";
Expand All @@ -78,4 +152,13 @@ void transform_withNamespace() {
assertThat(result.getJsonString(schema + TEST_KEY).getString()).isEqualTo(TEST_VALUE);
verify(context, never()).reportProblem(anyString());
}

private JsonObjectBuilder jsonObject() {
return jsonFactory.createObjectBuilder();

}

private JsonArrayBuilder jsonArray() {
return jsonFactory.createArrayBuilder();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public void initialize(ServiceExtensionContext context) {

var factory = Json.createBuilderFactory(Map.of());
managementApiTransformerRegistry.register(new JsonObjectFromContractAgreementTransformer(factory));
managementApiTransformerRegistry.register(new JsonObjectFromDataAddressTransformer(factory));
managementApiTransformerRegistry.register(new JsonObjectFromDataAddressTransformer(factory, typeManager, JSON_LD));
managementApiTransformerRegistry.register(new JsonObjectFromAssetTransformer(factory, typeManager, JSON_LD));
managementApiTransformerRegistry.register(new JsonObjectFromPolicyTransformer(factory, participantIdMapper));
managementApiTransformerRegistry.register(new JsonObjectFromQuerySpecTransformer(factory));
Expand Down
Loading