Skip to content

Commit

Permalink
feat: implement Asset specialization (#4297)
Browse files Browse the repository at this point in the history
* add isCatalog() method to Asset

* api ingress: handle edc:CatalogAsset

* change dcat:Catalog -> edc:CatalogAsset

* api egress: handle edc:CatalogAsset

* store isCatalog in the properties map

* add boolean equality operator predicate
  • Loading branch information
paullatzelsperger committed Jun 24, 2024
1 parent 596af5e commit 44299d4
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.util.List;
import java.util.Objects;

import static java.util.Optional.ofNullable;

public class EqualOperatorPredicate implements OperatorPredicate {

@Override
Expand All @@ -42,8 +44,13 @@ public boolean test(Object property, Object operandRight) {

if (property instanceof List<?> list) {
return list.stream().anyMatch(it -> Objects.equals(it, operandRight));
} else if (property instanceof Boolean booleanProperty) {
return ofNullable(operandRight)
.map(Object::toString)
.map(Boolean::parseBoolean)
.map(booleanOperand -> booleanOperand == booleanProperty)
.orElse(false);
}

return Objects.equals(property, operandRight);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ void shouldMatchPredicate_whenObjectIsList() {
assertThat(predicate.test(List.of("one", "two"), "three")).isFalse();
}

@Test
void shouldCheckName_whenPropertyIsBoolean() {
assertThat(predicate.test(Boolean.TRUE, "ENTRY2")).isFalse();
assertThat(predicate.test(Boolean.TRUE, "true")).isTrue();
assertThat(predicate.test(Boolean.TRUE, true)).isTrue();
assertThat(predicate.test(Boolean.TRUE, false)).isFalse();
assertThat(predicate.test(Boolean.TRUE, null)).isFalse();
assertThat(predicate.test(Boolean.TRUE, "false")).isFalse();
assertThat(predicate.test(true, false)).isFalse();
}

public enum TestEnum {
ENTRY1, ENTRY2
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ void shouldMatchPredicate_whenObjectIsList() {
assertThat(predicate.test(List.of("one", "two"), "three")).isTrue();
}

@Test
void shouldCheckName_whenPropertyIsBoolean() {
assertThat(predicate.test(Boolean.TRUE, "ENTRY2")).isTrue();
assertThat(predicate.test(Boolean.TRUE, "true")).isFalse();
assertThat(predicate.test(Boolean.TRUE, true)).isFalse();
assertThat(predicate.test(Boolean.TRUE, false)).isTrue();
assertThat(predicate.test(Boolean.TRUE, "false")).isTrue();
assertThat(predicate.test(Boolean.TRUE, null)).isTrue();
assertThat(predicate.test(true, false)).isTrue();
}

public enum TestEnum {
ENTRY1, ENTRY2
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_CATALOG_ASSET_TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;

Expand Down Expand Up @@ -52,6 +53,10 @@ public JsonObjectFromAssetTransformer(JsonBuilderFactory jsonFactory, ObjectMapp
builder.add(Asset.EDC_ASSET_PRIVATE_PROPERTIES, privatePropBuilder);
}

if (asset.isCatalog()) {
builder.add(TYPE, EDC_CATALOG_ASSET_TYPE);
}

if (asset.getDataAddress() != null) {
builder.add(Asset.EDC_ASSET_DATA_ADDRESS, context.transform(asset.getDataAddress(), JsonObject.class));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_DATA_ADDRESS;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_PRIVATE_PROPERTIES;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_PROPERTIES;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_CATALOG_ASSET_TYPE;
import static org.eclipse.edc.jsonld.spi.TypeUtil.nodeType;

/**
* Converts from an {@link Asset} as a {@link JsonObject} in JSON-LD expanded form to an {@link Asset}.
Expand All @@ -43,12 +45,20 @@ public JsonObjectToAssetTransformer() {
.id(nodeId(jsonObject));

visitProperties(jsonObject, key -> switch (key) {
case EDC_ASSET_PROPERTIES -> value -> visitProperties(value.asJsonArray().getJsonObject(0), property(context, builder));
case EDC_ASSET_PRIVATE_PROPERTIES -> value -> visitProperties(value.asJsonArray().getJsonObject(0), privateProperty(context, builder));
case EDC_ASSET_DATA_ADDRESS -> value -> builder.dataAddress(transformObject(value, DataAddress.class, context));
case EDC_ASSET_PROPERTIES ->
value -> visitProperties(value.asJsonArray().getJsonObject(0), property(context, builder));
case EDC_ASSET_PRIVATE_PROPERTIES ->
value -> visitProperties(value.asJsonArray().getJsonObject(0), privateProperty(context, builder));
case EDC_ASSET_DATA_ADDRESS ->
value -> builder.dataAddress(transformObject(value, DataAddress.class, context));
default -> doNothing();
});

// the asset is a Catalog Asset, i.e. it links to another catalog
if (EDC_CATALOG_ASSET_TYPE.equals(nodeType(jsonObject))) {
builder.property(Asset.PROPERTY_IS_CATALOG, true);
}

return builderResult(builder::build, context);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_DATA_ADDRESS;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_PRIVATE_PROPERTIES;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_PROPERTIES;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_CATALOG_ASSET_TYPE;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.PROPERTY_IS_CATALOG;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
Expand Down Expand Up @@ -132,6 +134,24 @@ void transform_customProperties_withCustomObject() {
assertThat(jsonObject.getJsonObject(EDC_ASSET_PROPERTIES).getJsonObject("https://foo.bar.org/schema/payload")).isInstanceOf(JsonObject.class);
}

@Test
void transform_shouldSetType_whenAssetIsCatalog() {
when(context.transform(isA(DataAddress.class), eq(JsonObject.class)))
.thenReturn(createObjectBuilder()
.add(EDC_DATA_ADDRESS_TYPE_PROPERTY, value("address-type"))
.build());
var dataAddress = DataAddress.Builder.newInstance().type("address-type").build();
var asset = createAssetBuilder()
.dataAddress(dataAddress)
.property(PROPERTY_IS_CATALOG, true)
.build();

var jsonObject = transformer.transform(asset, context);

assertThat(jsonObject).isNotNull();
assertThat(jsonObject.getString(TYPE)).isEqualTo(EDC_CATALOG_ASSET_TYPE);
}

private Asset.Builder createAssetBuilder() {
return Asset.Builder.newInstance()
.id(TEST_ASSET_ID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.map;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_PRIVATE_PROPERTIES;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_PROPERTIES;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_ASSET_TYPE;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.EDC_CATALOG_ASSET_TYPE;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.PROPERTY_CONTENT_TYPE;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.PROPERTY_DESCRIPTION;
import static org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset.PROPERTY_ID;
Expand Down Expand Up @@ -199,6 +201,22 @@ void shouldExcludeProperties_whenDefinedAtTheRootLevel() {
.asInstanceOf(map(String.class, Object.class)).doesNotContainKey(EDC_NAMESPACE + "thisShouldBeIgnored");
}

@Test
void shouldSetProperty_whenTypeIsCatalog() {
var jsonObj = jsonFactory.createObjectBuilder()
.add(CONTEXT, createContextBuilder().build())
.add(TYPE, EDC_CATALOG_ASSET_TYPE)
.add(ID, TEST_ASSET_ID)
.add(EDC_ASSET_PROPERTIES, createPropertiesBuilder().build())
.build();

var asset = typeTransformerRegistry.transform(TestInput.getExpanded(jsonObj), Asset.class);

assertThat(asset).withFailMessage(asset::getFailureDetail).isSucceeded();
assertThat(asset).isSucceeded()
.satisfies(a -> assertThat(a.isCatalog()).isTrue());
}

private JsonObjectBuilder createPayloadBuilder() {
return jsonFactory.createObjectBuilder()
.add(TYPE, "customPayload")
Expand Down
2 changes: 1 addition & 1 deletion docs/developer/management-domains/management-domains.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ The following implementation work will be done to support Management Domains.
The following changes will be made:

- A boolean subtype field will be introduced on `Asset` to indicate if it is a catalog. This field will be set to `true`
when an optional `@type` property is set to `dcat:Catalog` when an asset is created in the Management API.
when an optional `@type` property is set to `edc:CatalogAsset` when an asset is created in the Management API.
- The Management API will be updated in a backward-compatible way to handle optionally specifying the `@type` property
on `Asset`.
- `Catalog` will extend `Dataset`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ public class Asset extends Entity {
public static final String PROPERTY_DESCRIPTION = EDC_NAMESPACE + "description";
public static final String PROPERTY_VERSION = EDC_NAMESPACE + "version";
public static final String PROPERTY_CONTENT_TYPE = EDC_NAMESPACE + "contenttype";
public static final String PROPERTY_IS_CATALOG = EDC_NAMESPACE + "isCatalog";
public static final String EDC_ASSET_TYPE = EDC_NAMESPACE + "Asset";
public static final String EDC_CATALOG_ASSET_TYPE = EDC_NAMESPACE + "CatalogAsset";
public static final String EDC_ASSET_PROPERTIES = EDC_NAMESPACE + "properties";
public static final String EDC_ASSET_PRIVATE_PROPERTIES = EDC_NAMESPACE + "privateProperties";
public static final String EDC_ASSET_DATA_ADDRESS = EDC_NAMESPACE + "dataAddress";

private DataAddress dataAddress;
private final Map<String, Object> properties = new HashMap<>();
private final Map<String, Object> privateProperties = new HashMap<>();
private DataAddress dataAddress;

private Asset() {
}
Expand Down Expand Up @@ -79,6 +80,13 @@ public String getContentType() {
return ofNullable(getPropertyAsString(PROPERTY_CONTENT_TYPE)).orElse(getPropertyAsString("contenttype"));
}

@JsonIgnore
public boolean isCatalog() {
return ofNullable(getPropertyAsString(PROPERTY_IS_CATALOG))
.map(Boolean::parseBoolean)
.orElse(false);
}

public Map<String, Object> getProperties() {
return properties;
}
Expand All @@ -101,11 +109,6 @@ public Object getPrivateProperty(String key) {
return privateProperties.get(key);
}

private String getPropertyAsString(String key) {
var val = getProperty(key);
return val != null ? val.toString() : null;
}

public DataAddress getDataAddress() {
return dataAddress;
}
Expand All @@ -129,6 +132,11 @@ public boolean hasDuplicatePropertyKeys() {
return true;
}

private String getPropertyAsString(String key) {
var val = getProperty(key);
return val != null ? val.toString() : null;
}

@JsonPOJOBuilder(withPrefix = "")
public static class Builder extends Entity.Builder<Asset, Builder> {

Expand Down Expand Up @@ -159,6 +167,17 @@ public Builder self() {
return this;
}

@Override
public Asset build() {
super.build();

if (entity.getId() == null) {
id(UUID.randomUUID().toString());
}

return entity;
}

public Builder name(String title) {
entity.properties.put(PROPERTY_NAME, title);
return self();
Expand Down Expand Up @@ -205,17 +224,6 @@ public Builder privateProperty(String key, Object value) {
entity.privateProperties.put(key, value);
return self();
}

@Override
public Asset build() {
super.build();

if (entity.getId() == null) {
id(UUID.randomUUID().toString());
}

return entity;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,34 @@ void getNamedProperty_whenNotPresent_shouldReturnNull() {
assertThat(asset.getName()).isNull();
assertThat(asset.getVersion()).isNull();
}

@Test
void isCatalog_whenNotPresent_shouldReturnFalse() {
var asset = Asset.Builder.newInstance().build();
assertThat(asset.isCatalog()).isFalse();
}

@Test
void isCatalog_whenFalse_shouldReturnFalse() {
var asset = Asset.Builder.newInstance().property(Asset.PROPERTY_IS_CATALOG, "false").build();
assertThat(asset.isCatalog()).isFalse();

var asset2 = Asset.Builder.newInstance().property(Asset.PROPERTY_IS_CATALOG, false).build();
assertThat(asset2.isCatalog()).isFalse();
}

@Test
void isCatalog_whenTrue_shouldReturnTrue() {
var asset = Asset.Builder.newInstance().property(Asset.PROPERTY_IS_CATALOG, "true").build();
assertThat(asset.isCatalog()).isTrue();

var asset2 = Asset.Builder.newInstance().property(Asset.PROPERTY_IS_CATALOG, true).build();
assertThat(asset2.isCatalog()).isTrue();
}

@Test
void isCatalog_whenInvalidValid_shoudReturnFalse() {
var asset = Asset.Builder.newInstance().property(Asset.PROPERTY_IS_CATALOG, "foobar").build();
assertThat(asset.isCatalog()).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,20 @@ void shouldFail_whenAssetAlreadyExists() {
.usingRecursiveFieldByFieldElementComparator()
.contains(asset);
}

@Test
void shouldCreate_withPrivateProperty() {
var asset = createAssetBuilder("test-asset").privateProperty("prop1", "val1")
.property(Asset.PROPERTY_IS_CATALOG, true)
.build();

assertThat(getAssetIndex().create(asset).succeeded()).isTrue();
var assetFound = getAssetIndex().findById("test-asset");

assertThat(assetFound).isNotNull();
assertThat(assetFound.isCatalog()).isTrue();

}
}

@Nested
Expand Down
Loading

0 comments on commit 44299d4

Please sign in to comment.