Skip to content

Commit

Permalink
feat: implement dspace:DataAddress transformer
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Feb 29, 2024
1 parent d7c0245 commit 6a17cdb
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ backward compatibility to the existing format should be maintained by retaining
"dspace:dataAddress": {
"@type": "dspace:DataAddress",
"dspace:endpointType": "https://w3id.org/idsa/v4.1/HTTP",
"dspace:endpoint": {
"url": "http://example.com"
},
"dspace:endpoint": "http://example.com",
"dspace:endpointProperties": [
{
"@type": "dspace:EndpointProperty",
Expand All @@ -61,6 +59,8 @@ backward compatibility to the existing format should be maintained by retaining
Support for the optional DSP `authType` property will be added. If present, its value must be `bearer,` which indicates
clients must present the contained token as a bearer token to the associated HTTP endpoint.

_Note that all endpoints must be represented as URI-style strings._

## 3. Data Plane Authorization

Data Plane authorization involves creating an access token as part of the EDR when a `DataFlowStartMessage` is received
Expand All @@ -72,6 +72,7 @@ These operations will be encapsulated in the `DataPlaneAuthorizationService`:
```java
public interface DataPlaneAuthorizationService {
Result<DataAddress> createEndpointDataReference(DataFlowStartMessage message);

Result<DataAddress> authorize(String token, Map<String, Object> requestData);
}
```
Expand All @@ -89,14 +90,14 @@ public interface PublicEndpointGenerator {
Endpoint generateEndpoint(DataAddress sourceDataAddress);
}

public record Endpoint(Map<String, Object> endpoint, String endpointType) {
public record Endpoint(String endpoint, String endpointType) {


}
```

Note that the `endpoint` is an extensible (= schemaless) complex object because it could contain structured data, such
as a bucket name, folder path, prefix, etc. The `endpointType` is _always_ a String.
The shape of the `endpoint` is specific to each source type and must be documented out-of-band.
Note that the `endpoint` is represented as URI-style string. Thus, both `endpoint` and `endpointType` are _always_
Strings. The shape of the `endpoint` is specific to each source type and must be documented out-of-band.

### EDR and Token Creation

Expand All @@ -111,6 +112,7 @@ authorization servers:
```java
public interface DataPlaneAccessTokenService {
Result<TokenRepresentation> obtainToken(TokenParameters parameters, DataAddress address);

Result<AccessTokenData> resolve(String token);
}

Expand Down Expand Up @@ -156,7 +158,8 @@ encapsulates the token and transport-specific request information as a `Map`, e.

##### `AccessTokenData` Resolution

First, the `DataPlaneAuthorizationService.authorize()` implementation will invoke `DataPlaneAccessTokenService.resolve()`
First, the `DataPlaneAuthorizationService.authorize()` implementation will
invoke `DataPlaneAccessTokenService.resolve()`
to resolve the `AccessTokenData` containing the `DataAddress` and claims associated with the token:

```java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
import jakarta.json.Json;
import org.eclipse.edc.connector.api.signaling.transform.SignalingApiTransformerRegistry;
import org.eclipse.edc.connector.api.signaling.transform.SignalingApiTransformerRegistryImpl;
import org.eclipse.edc.connector.api.signaling.transform.from.JsonObjectFromDataFlowSuspendMessageTransformer;
import org.eclipse.edc.connector.api.signaling.transform.from.JsonObjectFromDataFlowTerminateMessageTransformer;
import org.eclipse.edc.connector.api.signaling.transform.to.JsonObjectToDataAddressTransformer;
import org.eclipse.edc.connector.api.signaling.transform.to.JsonObjectToDataFlowSuspendMessageTransformer;
import org.eclipse.edc.connector.api.signaling.transform.to.JsonObjectToDataFlowTerminateMessageTransformer;
import org.eclipse.edc.jsonld.spi.JsonLd;
Expand Down Expand Up @@ -97,10 +96,9 @@ public SignalingApiTransformerRegistry managementApiTypeTransformerRegistry() {
var factory = Json.createBuilderFactory(Map.of());

var registry = new SignalingApiTransformerRegistryImpl(this.transformerRegistry);
registry.register(new JsonObjectFromDataFlowSuspendMessageTransformer(factory));
registry.register(new JsonObjectToDataFlowSuspendMessageTransformer());
registry.register(new JsonObjectFromDataFlowTerminateMessageTransformer(factory));
registry.register(new JsonObjectToDataFlowTerminateMessageTransformer());
registry.register(new JsonObjectToDataAddressTransformer());
return registry;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.connector.api.signaling.transform;

import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_SCHEMA;

/**
* Contains constants specifically intended for serializing a {@link org.eclipse.edc.spi.types.domain.DataAddress}
* to JSON-LD using the `dspace:` prefix format.
*/
public interface DspaceDataAddressSerialization {
String DSPACE_DATAADDRESS_TYPE = DSPACE_SCHEMA + "DataAddress";
String ENDPOINT_TYPE_PROPERTY = DSPACE_SCHEMA + "endpointType";
String ENDPOINT_PROPERTY = DSPACE_SCHEMA + "endpoint";
String ENDPOINT_PROPERTIES_PROPERTY = DSPACE_SCHEMA + "endpointProperties";
String ENDPOINT_PROPERTY_PROPERTY_TYPE = DSPACE_SCHEMA + "EndpointProperty";
String ENDPOINT_PROPERTY_NAME_PROPERTY = DSPACE_SCHEMA + "name";
String ENDPOINT_PROPERTY_VALUE_PROPERTY = DSPACE_SCHEMA + "value";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.connector.api.signaling.transform;

public record DspaceEndpointProperty(String name, String value) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.connector.api.signaling.transform.to;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import org.eclipse.edc.connector.api.signaling.transform.DspaceDataAddressSerialization;
import org.eclipse.edc.connector.api.signaling.transform.DspaceEndpointProperty;
import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.transform.spi.TransformerContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.function.Consumer;
import java.util.function.Function;

import static org.eclipse.edc.connector.api.signaling.transform.DspaceDataAddressSerialization.ENDPOINT_PROPERTY_NAME_PROPERTY;
import static org.eclipse.edc.connector.api.signaling.transform.DspaceDataAddressSerialization.ENDPOINT_PROPERTY_VALUE_PROPERTY;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;

/**
* Transforms a {@link JsonObject} into a DataAddress using the DSPACE-serialization format.
*/
public class JsonObjectToDataAddressTransformer extends AbstractJsonLdTransformer<JsonObject, DataAddress> {
public JsonObjectToDataAddressTransformer() {
super(JsonObject.class, DataAddress.class);
}

@Override
public @Nullable DataAddress transform(@NotNull JsonObject jsonObject, @NotNull TransformerContext context) {
var builder = DataAddress.Builder.newInstance();
visitProperties(jsonObject, (s, v) -> transformProperties(s, v, builder, context));
return builder.build();
}

private void transformProperties(String key, JsonValue jsonValue, DataAddress.Builder builder, TransformerContext context) {
switch (key) {
case DspaceDataAddressSerialization.ENDPOINT_PROPERTY ->
transformString(jsonValue, endpoint -> builder.property("endpoint", endpoint), context);
case DspaceDataAddressSerialization.ENDPOINT_TYPE_PROPERTY -> {
var endpointType = transformString(jsonValue, context);
builder.type(endpointType);
builder.property("endpointType", endpointType); //todo: should we have this duplicated?
}
case DspaceDataAddressSerialization.ENDPOINT_PROPERTIES_PROPERTY ->
transformEndpointProperties(jsonValue, ep -> builder.property(ep.name(), ep.value()));
default -> throw new IllegalArgumentException("Unexpected value: " + key);
}
}

/**
* This method transforms a {@code dspace:EndpointProperties} array, which consists of {@code dspace:EndpointProperty} entries
* and invokes a consumer for each of those entries.
*
* @param jsonValue The endpointProperties JsonArray
* @param consumer A consumer that takes the {@link DspaceEndpointProperty} and processes it.
*/
private void transformEndpointProperties(JsonValue jsonValue, Consumer<DspaceEndpointProperty> consumer) {
Function<JsonObject, DspaceEndpointProperty> converter = (jo) -> {
var name = jo.getJsonArray(ENDPOINT_PROPERTY_NAME_PROPERTY).get(0).asJsonObject().get(VALUE);
var value = jo.getJsonArray(ENDPOINT_PROPERTY_VALUE_PROPERTY).get(0).asJsonObject().get(VALUE);
return new DspaceEndpointProperty(((JsonString) name).getString(), ((JsonString) value).getString());
};
if (jsonValue instanceof JsonObject object) {
consumer.accept(converter.apply(object));
}
if (jsonValue instanceof JsonArray array) {
// invoke the method recursively for every dspace:EndpointProperty entry
array.forEach(jv -> transformEndpointProperties(jv, consumer));
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* 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:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.connector.api.signaling.transform.to;

import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObjectBuilder;
import org.eclipse.edc.transform.spi.TransformerContext;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.edc.connector.api.signaling.transform.DspaceDataAddressSerialization.DSPACE_DATAADDRESS_TYPE;
import static org.eclipse.edc.connector.api.signaling.transform.DspaceDataAddressSerialization.ENDPOINT_PROPERTY_PROPERTY_TYPE;
import static org.eclipse.edc.connector.api.signaling.transform.TestFunctions.getExpanded;
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.VOCAB;
import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_PREFIX;
import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_SCHEMA;
import static org.mockito.Mockito.mock;

class JsonObjectToDataAddressTransformerTest {
private final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Map.of());
private final TransformerContext context = mock(TransformerContext.class);
private final JsonObjectToDataAddressTransformer transformer = new JsonObjectToDataAddressTransformer();

@Test
void transform() {
var jsonObj = jsonFactory.createObjectBuilder()
.add(CONTEXT, createContextBuilder().build())
.add(TYPE, DSPACE_DATAADDRESS_TYPE)
.add("endpointType", "https://w3id.org/idsa/v4.1/HTTP")
.add("endpoint", "http://example.com")
.add("endpointProperties", jsonFactory.createArrayBuilder()
.add(property("authorization", "some-token"))
.add(property("authType", "bearer"))
.add(property("foo", "bar"))
.add(property("fizz", "buzz"))
)
.build();

var expanded = getExpanded(jsonObj);

var dataAddress = transformer.transform(expanded, context);
assertThat(dataAddress).isNotNull();
assertThat(dataAddress.getType()).isEqualTo("https://w3id.org/idsa/v4.1/HTTP");
assertThat(dataAddress.getProperties())
.containsEntry("authorization", "some-token")
.containsEntry("authType", "bearer")
.containsEntry("fizz", "buzz")
.containsEntry("endpointType", dataAddress.getType())
.containsEntry("endpoint", "http://example.com");
}

@Test
void transform_withIllegalProperty() {
var jsonObj = jsonFactory.createObjectBuilder()
.add(CONTEXT, createContextBuilder().build())
.add(TYPE, DSPACE_DATAADDRESS_TYPE)
.add("endpointType", "https://w3id.org/idsa/v4.1/HTTP")
.add("endpoint", "http://example.com")
.add("endpointProperties", jsonFactory.createArrayBuilder()
.add(property("fizz", "buzz"))
)
.add("rogueProperty", 42L)
.build();

var expanded = getExpanded(jsonObj);

assertThatThrownBy(() -> transformer.transform(expanded, context))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unexpected value: %srogueProperty".formatted(DSPACE_SCHEMA));
}

private JsonObjectBuilder property(String key, String value) {
return jsonFactory.createObjectBuilder()
.add(TYPE, ENDPOINT_PROPERTY_PROPERTY_TYPE)
.add("name", key)
.add("value", value);
}

private JsonArrayBuilder createContextBuilder() {
return jsonFactory.createArrayBuilder()
.add(jsonFactory.createObjectBuilder().add(VOCAB, DSPACE_SCHEMA))
.add(jsonFactory.createObjectBuilder().add(DSPACE_PREFIX, DSPACE_SCHEMA));
}

}

0 comments on commit 6a17cdb

Please sign in to comment.