diff --git a/DEPENDENCIES b/DEPENDENCIES index 72d2914..66149b9 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -18,9 +18,9 @@ maven/mavencentral/commons-beanutils/commons-beanutils/1.9.4, Apache-2.0, approv maven/mavencentral/commons-collections/commons-collections/3.2.2, Apache-2.0, approved, #15185 maven/mavencentral/info.picocli/picocli/4.6.2, Apache-2.0, approved, clearlydefined maven/mavencentral/jakarta.json/jakarta.json-api/2.1.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jsonp -maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.14.8, Apache-2.0, approved, #7164 +maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.15.0, Apache-2.0, approved, #16009 maven/mavencentral/net.bytebuddy/byte-buddy/1.14.18, Apache-2.0 AND BSD-3-Clause, approved, #7163 -maven/mavencentral/net.bytebuddy/byte-buddy/1.14.8, Apache-2.0 AND BSD-3-Clause, approved, #7163 +maven/mavencentral/net.bytebuddy/byte-buddy/1.15.0, Apache-2.0 AND BSD-3-Clause, approved, #16008 maven/mavencentral/net.sf.saxon/Saxon-HE/10.6, MPL-2.0 AND W3C, approved, #7945 maven/mavencentral/org.antlr/antlr4-runtime/4.9.3, BSD-3-Clause, approved, #322 maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined @@ -51,7 +51,7 @@ maven/mavencentral/org.junit.platform/junit-platform-suite-api/1.10.3, EPL-2.0, maven/mavencentral/org.junit.platform/junit-platform-suite-commons/1.10.3, EPL-2.0, approved, #15214 maven/mavencentral/org.junit.platform/junit-platform-suite-engine/1.10.3, EPL-2.0, approved, #15258 maven/mavencentral/org.junit/junit-bom/5.10.3, EPL-2.0, approved, #9844 -maven/mavencentral/org.mockito/mockito-core/5.6.0, MIT AND (Apache-2.0 AND MIT) AND Apache-2.0, approved, #10932 +maven/mavencentral/org.mockito/mockito-core/5.13.0, MIT, approved, clearlydefined maven/mavencentral/org.objenesis/objenesis/3.3, Apache-2.0, approved, clearlydefined maven/mavencentral/org.opentest4j/opentest4j/1.3.0, Apache-2.0, approved, #9713 maven/mavencentral/org.ow2.asm/asm-commons/9.6, BSD-3-Clause, approved, #10775 diff --git a/README.md b/README.md index 67db776..283d61b 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -# Compliance Verfication Framework +# Compliance Verification Framework + +To build: + +```bash +./gradlew clean build +``` + +To execute: + +```bash +java -jar runtimes/dsp-tck/build/libs/dsp-tck-runtime.jar -config config/tck/sample.tck.properties +``` \ No newline at end of file diff --git a/boot/build.gradle.kts b/boot/build.gradle.kts new file mode 100644 index 0000000..3ab7919 --- /dev/null +++ b/boot/build.gradle.kts @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + * + */ + + diff --git a/boot/src/main/java/org/eclipse/dataspacetck/core/spi/boot/Monitor.java b/boot/src/main/java/org/eclipse/dataspacetck/core/spi/boot/Monitor.java new file mode 100644 index 0000000..cb1098e --- /dev/null +++ b/boot/src/main/java/org/eclipse/dataspacetck/core/spi/boot/Monitor.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ +package org.eclipse.dataspacetck.core.spi.boot; + +/** + * Sends formatted messages to the system output. + */ +public interface Monitor { + + Monitor enableSuccess(); + + Monitor enableError(); + + Monitor enableBold(); + + Monitor resetMode(); + + Monitor newLine(); + + Monitor message(String message); + + Monitor debug(String message); +} diff --git a/build.gradle.kts b/build.gradle.kts index 5823c12..a8b29bf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ allprojects { tasks.test { useJUnitPlatform() - systemProperty("cvf.launcher", "org.eclipse.dataspacetck.dsp.system.DspSystemLauncher") + systemProperty("dataspacetck.launcher", "org.eclipse.dataspacetck.dsp.system.DspSystemLauncher") } tasks.jar { diff --git a/config/tck/sample.tck.properties b/config/tck/sample.tck.properties new file mode 100644 index 0000000..d54c1c7 --- /dev/null +++ b/config/tck/sample.tck.properties @@ -0,0 +1,29 @@ +# Contains sample configuration options +dataspacetck.dsp.local.connector=false +dataspacetck.debug=true +dataspacetck.dsp.connector.http.url=http://localhost:8282/api/v1/dsp/ + +# Sets the dataset and offer ids to use for contract negotiation scenarios +CN_01_01_DATASETID=ACN0101 +CN_01_01_OFFERID=CD123:ACN0101:456 +CN_01_02_DATASETID=ACN0102 +CN_01_02_OFFERID=CD123:ACN0102:456 +CN_01_03_DATASETID=ACN0103 +CN_01_03_OFFERID=CD123:ACN0103:456 +CN_01_04_DATASETID=ACN0104 +CN_01_04_OFFERID=CD123:ACN0104:456 + +CN_02_01_DATASETID=ACN0201 +CN_02_01_OFFERID=CD123:ACN0201:456 +CN_02_02_DATASETID=ACN0202 +CN_02_02_OFFERID=CD123:ACN0202:456 +CN_02_03_DATASETID=ACN0203 +CN_02_03_OFFERID=CD123:ACN0203:456 +CN_02_04_DATASETID=ACN0204 +CN_02_04_OFFERID=CD123:ACN0204:456 +CN_02_05_DATASETID=ACN0205 +CN_02_05_OFFERID=CD123:ACN0205:456 +CN_02_06_DATASETID=ACN0206 +CN_02_06_OFFERID=CD123:ACN0206:456 +CN_02_07_DATASETID=ACN0207 +CN_02_07_OFFERID=CD123:ACN0207:456 diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 99e46cf..586c98c 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -12,5 +12,7 @@ * * */ - +dependencies { + api(project(":boot")) +} diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/api/message/MessageSerializer.java b/core/src/main/java/org/eclipse/dataspacetck/core/api/message/MessageSerializer.java index 0587861..c1dc994 100644 --- a/core/src/main/java/org/eclipse/dataspacetck/core/api/message/MessageSerializer.java +++ b/core/src/main/java/org/eclipse/dataspacetck/core/api/message/MessageSerializer.java @@ -16,6 +16,7 @@ package org.eclipse.dataspacetck.core.api.message; import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.JsonLdOptions; import com.apicatalog.jsonld.document.JsonDocument; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -60,29 +61,35 @@ public static String serialize(Object object) { } } - public static Map processJsonLd(InputStream stream) { + public static Map processJsonLd(InputStream stream, Map context) { try { - return processJsonLd(JsonDocument.of(MAPPER.readValue(stream, JsonObject.class))); + return processJsonLd(MAPPER.readValue(stream, JsonObject.class), context); } catch (IOException e) { throw new RuntimeException(e); } } - public static Map processJsonLd(Map message) { - return processJsonLd(JsonDocument.of(MAPPER.convertValue(message, JsonObject.class))); + public static Map processJsonLd(Map message, Map context) { + return processJsonLd(MAPPER.convertValue(message, JsonObject.class), context); } @SuppressWarnings("unchecked") - private static Map processJsonLd(JsonDocument document) { + private static Map processJsonLd(JsonObject document, Map context) { try { - var jsonArray = expand(document).get(); + var options = new JsonLdOptions(); + options.setExpandContext(MAPPER.convertValue(context, JsonObject.class)); + options.setCompactArrays(true); + var jsonArray = expand(JsonDocument.of(document)).options(options).get(); if (jsonArray.isEmpty()) { throw new AssertionError("Invalid Json document, expecting a non-empty array"); } @SuppressWarnings("SequencedCollectionMethodCanBeUsed") var expanded = jsonArray.get(0); - var compacted = compact(JsonDocument.of(MAPPER.convertValue(expanded, JsonObject.class)), EMPTY_CONTEXT).get(); - return MAPPER.convertValue(compacted, Map.class); + return MAPPER.convertValue(expanded, Map.class); + + //var compacted = compact(JsonDocument.of(MAPPER.convertValue(expanded, JsonObject.class)), EMPTY_CONTEXT).get(); + + // return MAPPER.convertValue(compacted, Map.class); } catch (JsonLdError e) { throw new RuntimeException(e); } diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/api/system/CallbackEndpoint.java b/core/src/main/java/org/eclipse/dataspacetck/core/api/system/CallbackEndpoint.java index 3972497..648ce9e 100644 --- a/core/src/main/java/org/eclipse/dataspacetck/core/api/system/CallbackEndpoint.java +++ b/core/src/main/java/org/eclipse/dataspacetck/core/api/system/CallbackEndpoint.java @@ -14,6 +14,7 @@ package org.eclipse.dataspacetck.core.api.system; +import java.io.InputStream; import java.util.function.Function; /** @@ -29,7 +30,7 @@ public interface CallbackEndpoint { /** * Registers a response handler for the given callback path. */ - void registerHandler(String path, Function consumer); + void registerHandler(String path, Function consumer); /** * Deregisters a response handler. diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/api/system/SystemsConstants.java b/core/src/main/java/org/eclipse/dataspacetck/core/api/system/SystemsConstants.java index 57daf40..073ac9b 100644 --- a/core/src/main/java/org/eclipse/dataspacetck/core/api/system/SystemsConstants.java +++ b/core/src/main/java/org/eclipse/dataspacetck/core/api/system/SystemsConstants.java @@ -18,6 +18,8 @@ * Constants for system configuration. */ public interface SystemsConstants { - String CVF_CALLBACK_ADDRESS = "cvf.callback.address"; - String CVF_LAUNCHER = "cvf.launcher"; + String TCK_PREFIX = "dataspacetck"; + String TCK_CALLBACK_ADDRESS = TCK_PREFIX + ".callback.address"; + String TCK_DEFAULT_CALLBACK_ADDRESS = "http://localhost:8083"; + String TCK_LAUNCHER = TCK_PREFIX + ".launcher"; } diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/spi/system/AbstractConfiguration.java b/core/src/main/java/org/eclipse/dataspacetck/core/spi/system/AbstractConfiguration.java index 092e241..a37325e 100644 --- a/core/src/main/java/org/eclipse/dataspacetck/core/spi/system/AbstractConfiguration.java +++ b/core/src/main/java/org/eclipse/dataspacetck/core/spi/system/AbstractConfiguration.java @@ -15,12 +15,15 @@ package org.eclipse.dataspacetck.core.spi.system; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; + import java.util.HashMap; import java.util.Map; import java.util.function.Function; import static java.lang.Boolean.parseBoolean; import static java.lang.Integer.parseInt; +import static java.lang.Long.parseLong; /** * Configuration used to start a {@link SystemLauncher}. @@ -28,6 +31,11 @@ public abstract class AbstractConfiguration { protected Function propertyDelegate = k -> null; protected Map extensibleConfiguration = new HashMap<>(); + protected Monitor monitor; + + public Monitor getMonitor() { + return monitor; + } public String getPropertyAsString(String key, String defaultValue) { var value = getProperty(key); @@ -39,6 +47,11 @@ public int getPropertyAsInt(String key, int defaultValue) { return value != null ? parseInt(value) : defaultValue; } + public long getPropertyAsLong(String key, long defaultValue) { + var value = getProperty(key); + return value != null ? parseLong(value) : defaultValue; + } + public boolean getPropertyAsBoolean(String key, boolean defaultValue) { var value = getProperty(key); return value != null ? parseBoolean(value) : defaultValue; @@ -57,13 +70,18 @@ private String getProperty(String key) { public abstract static class Builder> { + @SuppressWarnings("unchecked") + public B monitor(Monitor monitor) { + getConfiguration().monitor = monitor; + return (B) this; + } + @SuppressWarnings("unchecked") public B property(String key, String value) { getConfiguration().extensibleConfiguration.put(key, value); return (B) this; } - @SuppressWarnings("unchecked") public B propertyDelegate(Function delegate) { getConfiguration().propertyDelegate = delegate; diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/system/ConfigFunctions.java b/core/src/main/java/org/eclipse/dataspacetck/core/system/ConfigFunctions.java new file mode 100644 index 0000000..40dc228 --- /dev/null +++ b/core/src/main/java/org/eclipse/dataspacetck/core/system/ConfigFunctions.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.dataspacetck.core.system; + +/** + * Configuration manipulation functions. + */ +public class ConfigFunctions { + + /** + * Returns a configuration value by checking system properties and then env variables. Keys are converted to uppercase and + * "." is replaced by "_" when checking for env variables. + */ + public static String propertyOrEnv(String key, String defaultValue) { + var value = System.getProperty(key); + if (exists(value)) { + return value; + } + var upperKey = key.toUpperCase().replace('.', '_'); + value = System.getenv(upperKey); + if (exists(value)) { + return value; + } + return defaultValue; + } + + private static boolean exists(String value) { + return value != null && !value.trim().isEmpty(); + } + + private ConfigFunctions() { + } +} diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/system/ConsoleMonitor.java b/core/src/main/java/org/eclipse/dataspacetck/core/system/ConsoleMonitor.java new file mode 100644 index 0000000..a87cf3e --- /dev/null +++ b/core/src/main/java/org/eclipse/dataspacetck/core/system/ConsoleMonitor.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.dataspacetck.core.system; + +import org.eclipse.dataspacetck.core.spi.boot.Monitor; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import static org.eclipse.dataspacetck.core.api.system.SystemsConstants.TCK_PREFIX; + +/** + * A monitor that sends messages to standard out. + */ +public class ConsoleMonitor implements Monitor { + public static final String ANSI_PROPERTY = TCK_PREFIX + ".ansi"; + public static final String DEBUG_PROPERTY = TCK_PREFIX + ".debug"; + + private final boolean debug; + private final boolean ansi; + + public ConsoleMonitor(boolean debug, boolean ansi) { + this.debug = debug; + this.ansi = ansi; + } + + @Override + public Monitor newLine() { + System.out.println(); + return this; + } + + @Override + public Monitor debug(String message) { + if (debug) { + message(message); + } + return this; + } + + @Override + public Monitor message(String message) { + var time = ZonedDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + System.out.printf("[%s] %s%n", time, message); + return this; + } + + @Override + public Monitor enableSuccess() { + System.out.println(ansiSuccess()); + return this; + } + + @Override + public Monitor enableError() { + System.out.println(ansiError()); + return this; + } + + @Override + public Monitor enableBold() { + System.out.println(ansiBold()); + return this; + } + + @Override + public Monitor resetMode() { + System.out.println(ansiReset()); + return this; + } + + private String ansiError() { + return ansi ? "\u001B[31m" : ""; + } + + private String ansiSuccess() { + return ansi ? "\u001B[32m" : ""; + } + + private String ansiReset() { + return ansi ? "\u001B[0m" : ""; + } + + private String ansiBold() { + return ansi ? "\u001B[1m" : ""; + } + +} diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/system/DefaultCallbackEndpoint.java b/core/src/main/java/org/eclipse/dataspacetck/core/system/DefaultCallbackEndpoint.java index 321b30f..51186cb 100644 --- a/core/src/main/java/org/eclipse/dataspacetck/core/system/DefaultCallbackEndpoint.java +++ b/core/src/main/java/org/eclipse/dataspacetck/core/system/DefaultCallbackEndpoint.java @@ -18,6 +18,7 @@ import org.eclipse.dataspacetck.core.api.system.CallbackEndpoint; import org.junit.jupiter.api.extension.ExtensionContext; +import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -32,9 +33,9 @@ /** * Implements a callback endpoint. *

- * Deserialized messages from incoming transports such as HTTP are dispatched to a registered handler through this endpoint by calling {@link #apply(String, Object)}. + * Deserialized messages from incoming transports such as HTTP are dispatched to a registered handler through this endpoint by calling {@link #apply(String, InputStream)}. */ -class DefaultCallbackEndpoint implements CallbackEndpoint, BiFunction, ExtensionContext.Store.CloseableResource { +public class DefaultCallbackEndpoint implements CallbackEndpoint, BiFunction, ExtensionContext.Store.CloseableResource { @FunctionalInterface public interface LifecycleListener { @@ -43,7 +44,7 @@ public interface LifecycleListener { private String address; private List listeners = new ArrayList<>(); - private Map> handlers = new HashMap<>(); + private Map> handlers = new HashMap<>(); @Override @@ -56,13 +57,13 @@ public boolean handlesPath(String path) { } @Override - public Object apply(String path, Object message) { + public String apply(String path, InputStream message) { //noinspection OptionalGetWithoutIsPresent return lookupHandler(path).get().apply(message); } @Override - public void registerHandler(String path, Function handler) { + public void registerHandler(String path, Function handler) { if (!path.startsWith("/")) { path = "/" + path; } @@ -112,7 +113,7 @@ private Builder() { /** * Matches the path based on the regular expression. */ - private Optional> lookupHandler(String expression) { + private Optional> lookupHandler(String expression) { var pattern = compile(expression); return handlers.entrySet() .stream() diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/system/injection/InstanceInjector.java b/core/src/main/java/org/eclipse/dataspacetck/core/system/InstanceInjector.java similarity index 93% rename from core/src/main/java/org/eclipse/dataspacetck/core/system/injection/InstanceInjector.java rename to core/src/main/java/org/eclipse/dataspacetck/core/system/InstanceInjector.java index 818b640..7043a4c 100644 --- a/core/src/main/java/org/eclipse/dataspacetck/core/system/injection/InstanceInjector.java +++ b/core/src/main/java/org/eclipse/dataspacetck/core/system/InstanceInjector.java @@ -13,7 +13,7 @@ * */ -package org.eclipse.dataspacetck.core.system.injection; +package org.eclipse.dataspacetck.core.system; import org.eclipse.dataspacetck.core.api.system.ConfigParam; import org.eclipse.dataspacetck.core.api.system.Inject; @@ -26,6 +26,7 @@ import static java.lang.String.format; import static java.util.Arrays.stream; +import static org.eclipse.dataspacetck.core.system.ConfigFunctions.propertyOrEnv; /** * Injects fields on an instance annotated with {@link Inject} in a type hierarchy. @@ -71,7 +72,7 @@ private void visitConfigField(Field field, Object instance) { return; } String key = getKey(field); - var value = System.getProperty(key); + var value = extensionContext.getConfigurationParameter(key).orElse(propertyOrEnv(key, null)); if (value == null) { if (((ConfigParam) annotation.get()).required()) { var className = field.getDeclaringClass().getName(); @@ -99,7 +100,7 @@ private boolean visitInjectField(Field field, Object instance) { .tags(tags) .scopeId(id) .annotations(annotations) - .propertyDelegate(k -> extensionContext.getConfigurationParameter(k).orElse(null)) + .propertyDelegate(k -> extensionContext.getConfigurationParameter(k).orElse(propertyOrEnv(k, null))) .build(); try { diff --git a/core/src/main/java/org/eclipse/dataspacetck/core/system/SystemBootstrapExtension.java b/core/src/main/java/org/eclipse/dataspacetck/core/system/SystemBootstrapExtension.java index de02253..82336ff 100644 --- a/core/src/main/java/org/eclipse/dataspacetck/core/system/SystemBootstrapExtension.java +++ b/core/src/main/java/org/eclipse/dataspacetck/core/system/SystemBootstrapExtension.java @@ -15,19 +15,13 @@ package org.eclipse.dataspacetck.core.system; -import com.apicatalog.jsonld.JsonLd; -import com.apicatalog.jsonld.JsonLdError; -import com.apicatalog.jsonld.document.JsonDocument; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import org.eclipse.dataspacetck.core.api.message.MessageSerializer; import org.eclipse.dataspacetck.core.api.system.CallbackEndpoint; -import org.eclipse.dataspacetck.core.api.system.SystemsConstants; import org.eclipse.dataspacetck.core.spi.system.ServiceConfiguration; import org.eclipse.dataspacetck.core.spi.system.SystemConfiguration; import org.eclipse.dataspacetck.core.spi.system.SystemLauncher; -import org.eclipse.dataspacetck.core.system.injection.InstanceInjector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -41,21 +35,33 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; +import java.net.URI; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; - +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static java.lang.Boolean.parseBoolean; +import static org.eclipse.dataspacetck.core.api.system.SystemsConstants.TCK_CALLBACK_ADDRESS; +import static org.eclipse.dataspacetck.core.api.system.SystemsConstants.TCK_DEFAULT_CALLBACK_ADDRESS; +import static org.eclipse.dataspacetck.core.api.system.SystemsConstants.TCK_LAUNCHER; +import static org.eclipse.dataspacetck.core.system.ConfigFunctions.propertyOrEnv; +import static org.eclipse.dataspacetck.core.system.ConsoleMonitor.ANSI_PROPERTY; +import static org.eclipse.dataspacetck.core.system.ConsoleMonitor.DEBUG_PROPERTY; import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; public class SystemBootstrapExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver, ExtensionContext.Store.CloseableResource { - private static final ExtensionContext.Namespace CALLBACK_NAMESPACE = org.junit.jupiter.api.extension.ExtensionContext.Namespace.create(new Object()); private static boolean started; + private String callbackHost; + private int callbackPort; private static SystemLauncher launcher; private static DispatchingHandler dispatchingHandler; private static HttpServer server; + private ExecutorService executorService; @Override public void beforeAll(ExtensionContext context) { @@ -66,14 +72,24 @@ public void beforeAll(ExtensionContext context) { context.getRoot().getStore(GLOBAL).put(SystemBootstrapExtension.class.getName() + "-initialized", this); launcher = initializeLauncher(context); + + var ansi = parseBoolean(context.getConfigurationParameter(ANSI_PROPERTY).orElse(propertyOrEnv(ANSI_PROPERTY, "true"))); + var debug = parseBoolean(context.getConfigurationParameter(DEBUG_PROPERTY).orElse(propertyOrEnv(DEBUG_PROPERTY, "false"))); + var callbackAddress = URI.create(context.getConfigurationParameter(TCK_CALLBACK_ADDRESS).orElse(propertyOrEnv(TCK_CALLBACK_ADDRESS, TCK_DEFAULT_CALLBACK_ADDRESS))); + + this.callbackHost = callbackAddress.getHost(); + this.callbackPort = callbackAddress.getPort(); + var configuration = SystemConfiguration.Builder.newInstance() - .propertyDelegate(k -> context.getConfigurationParameter(k).orElse(null)) + .propertyDelegate(k -> context.getConfigurationParameter(k).orElse(propertyOrEnv(k, null))) + .monitor(new ConsoleMonitor(debug, ansi)) .build(); launcher.start(configuration); dispatchingHandler = new DispatchingHandler(); - server = initializeCallbackServer(dispatchingHandler); + executorService = Executors.newFixedThreadPool(1); + server = initializeCallbackServer(dispatchingHandler, executorService); server.start(); } @@ -91,6 +107,9 @@ public void close() { if (server != null) { server.stop(0); } + if (executorService != null) { + executorService.shutdown(); + } } @Override @@ -112,7 +131,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte .tags(tags) .scopeId(id) .annotations(parameterContext.getParameter().getAnnotations()) - .propertyDelegate(k -> context.getConfigurationParameter(k).orElse(null)) + .propertyDelegate(k -> context.getConfigurationParameter(k).orElse(propertyOrEnv(k, null))) .build(); service = launcher.getService(type, configuration, (t, c) -> resolve(t, context)); if (service != null) { @@ -146,7 +165,8 @@ private Object resolve(Class type, ExtensionContext context) { private CallbackEndpoint attachCallbackEndpoint(DispatchingHandler dispatchingHandler, ExtensionContext context) { var endpointBuilder = DefaultCallbackEndpoint.Builder.newInstance(); - endpointBuilder.address(context.getConfigurationParameter(SystemsConstants.CVF_CALLBACK_ADDRESS).orElse("http://localhost:8083")); // xcv + endpointBuilder.address(context.getConfigurationParameter(TCK_CALLBACK_ADDRESS) + .orElse(propertyOrEnv(TCK_CALLBACK_ADDRESS, TCK_DEFAULT_CALLBACK_ADDRESS))); endpointBuilder.listener(dispatchingHandler::deregisterEndpoint); var endpoint = endpointBuilder.build(); @@ -155,23 +175,25 @@ private CallbackEndpoint attachCallbackEndpoint(DispatchingHandler dispatchingHa } private SystemLauncher initializeLauncher(ExtensionContext context) { - var launcherClass = context.getConfigurationParameter(SystemsConstants.CVF_LAUNCHER); + var launcherClass = context.getConfigurationParameter(TCK_LAUNCHER).orElse(propertyOrEnv(TCK_LAUNCHER, null)); - if (launcherClass.isEmpty()) { + if (launcherClass == null) { return new NoOpSystemLauncher(); } else { try { - return (SystemLauncher) getClass().getClassLoader().loadClass(launcherClass.get()).getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { - throw new RuntimeException("Unable to create Launcher class: " + launcherClass.get(), e); + return (SystemLauncher) getClass().getClassLoader().loadClass(launcherClass).getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | + IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException("Unable to create Launcher class: " + launcherClass, e); } } } - private HttpServer initializeCallbackServer(HttpHandler rootHandler) { + private HttpServer initializeCallbackServer(HttpHandler rootHandler, ExecutorService executorService) { try { - server = HttpServer.create(new InetSocketAddress(8083), 0); // XCV align with callback address + server = HttpServer.create(new InetSocketAddress(callbackHost, callbackPort), 0); server.createContext("/", rootHandler); + server.setExecutor(executorService); return server; } catch (IOException e) { throw new RuntimeException(e); @@ -194,21 +216,15 @@ public void handle(HttpExchange exchange) throws IOException { var path = exchange.getRequestURI().getPath(); for (var endpoint : endpoints) { if (endpoint.handlesPath(path)) { - try { - var compacted = JsonLd.compact(JsonDocument.of(exchange.getRequestBody()), MessageSerializer.EMPTY_CONTEXT); - var message = MessageSerializer.MAPPER.convertValue(compacted.get(), Object.class); - var response = endpoint.apply(path, message); - if (response == null) { - exchange.sendResponseHeaders(200, 0); - } else { - var serialized = MessageSerializer.serialize(response).getBytes(); - exchange.sendResponseHeaders(200, serialized.length); - var responseBody = exchange.getResponseBody(); - responseBody.write(serialized); - responseBody.close(); - } - } catch (JsonLdError e) { - throw new RuntimeException(e); + var response = endpoint.apply(path, exchange.getRequestBody()); + if (response == null) { + exchange.sendResponseHeaders(200, 0); + } else { + var bytes = response.getBytes(); + exchange.sendResponseHeaders(200, bytes.length); + var responseBody = exchange.getResponseBody(); + responseBody.write(bytes); + responseBody.close(); } return; } diff --git a/core/src/test/java/org/eclipse/dataspacetck/core/system/ConfigFunctionsTest.java b/core/src/test/java/org/eclipse/dataspacetck/core/system/ConfigFunctionsTest.java new file mode 100644 index 0000000..a090b68 --- /dev/null +++ b/core/src/test/java/org/eclipse/dataspacetck/core/system/ConfigFunctionsTest.java @@ -0,0 +1,47 @@ +package org.eclipse.dataspacetck.core.system; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +class ConfigFunctionsTest { + private static final String PROP_1 = "PROP1"; + private static final String PROP_1_VALUE = "PROP_1_VALUE"; + + @BeforeAll + static void beforeAll() { + System.setProperty(PROP_1, PROP_1_VALUE); + } + + @AfterAll + static void afterAll() { + System.clearProperty(PROP_1); + } + + @Test + void verifySettings() { + assertThat(ConfigFunctions.propertyOrEnv("notthere", "default")).isEqualTo("default"); + assertThat(ConfigFunctions.propertyOrEnv(PROP_1, "notthere")).isEqualTo(PROP_1_VALUE); + + if (!System.getenv().isEmpty()) { + var entry = System.getenv().entrySet().iterator().next(); + assertThat(ConfigFunctions.propertyOrEnv(entry.getKey().toLowerCase(), null)).isEqualTo(entry.getValue()); + } + } +} \ No newline at end of file diff --git a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/AbstractContractNegotiationProviderTest.java b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/AbstractContractNegotiationProviderTest.java new file mode 100644 index 0000000..55db615 --- /dev/null +++ b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/AbstractContractNegotiationProviderTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 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.dataspacetck.dsp.verification.cn; + +import org.eclipse.dataspacetck.core.api.system.ConfigParam; +import org.eclipse.dataspacetck.core.api.system.Inject; +import org.eclipse.dataspacetck.core.api.verification.AbstractVerificationTest; +import org.eclipse.dataspacetck.dsp.system.api.connector.Connector; +import org.eclipse.dataspacetck.dsp.system.api.connector.Consumer; +import org.eclipse.dataspacetck.dsp.system.api.mock.NegotiationProviderMock; +import org.eclipse.dataspacetck.dsp.system.api.pipeline.NegotiationPipeline; +import org.junit.jupiter.api.Tag; + +import static java.util.UUID.randomUUID; + +@Tag("dsp-cn") +public abstract class AbstractContractNegotiationProviderTest extends AbstractVerificationTest { + + @Inject + @Consumer + protected Connector consumerConnector; + + @Inject + protected NegotiationPipeline negotiationPipeline; + + @Inject + protected NegotiationProviderMock negotiationMock; + + @ConfigParam + protected String offerId = randomUUID().toString(); + + @ConfigParam + protected String datasetId = randomUUID().toString(); + +} diff --git a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/AbstractNegotiationVerificationTest.java b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/AbstractNegotiationVerificationTest.java deleted file mode 100644 index d855bd2..0000000 --- a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/AbstractNegotiationVerificationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023 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.dataspacetck.dsp.verification.cn; - -import org.eclipse.dataspacetck.core.api.verification.AbstractVerificationTest; -import org.junit.jupiter.api.Tag; - -@Tag("dsp-cn") -public abstract class AbstractNegotiationVerificationTest extends AbstractVerificationTest { -} diff --git a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/DspContractNegotiationMandatory01Test.java b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider01Test.java similarity index 55% rename from dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/DspContractNegotiationMandatory01Test.java rename to dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider01Test.java index 84f2ff0..e225656 100644 --- a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/DspContractNegotiationMandatory01Test.java +++ b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider01Test.java @@ -15,17 +15,10 @@ package org.eclipse.dataspacetck.dsp.verification.cn; -import org.eclipse.dataspacetck.core.api.system.ConfigParam; -import org.eclipse.dataspacetck.core.api.system.Inject; import org.eclipse.dataspacetck.core.api.system.MandatoryTest; -import org.eclipse.dataspacetck.dsp.system.api.connector.Connector; -import org.eclipse.dataspacetck.dsp.system.api.connector.Consumer; -import org.eclipse.dataspacetck.dsp.system.api.mock.NegotiationProviderMock; -import org.eclipse.dataspacetck.dsp.system.api.pipeline.NegotiationPipeline; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; -import static java.util.UUID.randomUUID; import static org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation.State.AGREED; import static org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation.State.FINALIZED; import static org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation.State.OFFERED; @@ -33,33 +26,17 @@ @Tag("base-compliance") @DisplayName("CN_01: Contract request scenarios") -public class DspContractNegotiationMandatory01Test extends AbstractNegotiationVerificationTest { - - @Inject - @Consumer - private Connector clientConnector; - - @Inject - private NegotiationPipeline negotiationPipeline; - - @Inject - protected NegotiationProviderMock negotiationMock; - - @ConfigParam - protected String offerId = randomUUID().toString(); - - @ConfigParam - protected String datasetId = randomUUID().toString(); +public class ContractNegotiationProvider01Test extends AbstractContractNegotiationProviderTest { @MandatoryTest - @DisplayName("Verify contract request, offer received, consumer terminated") + @DisplayName("CN:01-01: Verify contract request, offer received, consumer terminated") public void cn_01_01() { negotiationMock.recordContractRequestedAction(ProviderActions::postOffer); negotiationPipeline - .expectOffer(offer -> clientConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) - .sendRequest(datasetId, offerId, datasetId) + .expectOffer(offer -> consumerConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) + .sendRequest(datasetId, offerId) .thenWaitForState(OFFERED) .sendTermination() .thenVerifyProviderState(TERMINATED) @@ -69,18 +46,18 @@ public void cn_01_01() { } @MandatoryTest - @DisplayName("Verify contract request, offer received, consumer counter-offer, provider terminated") + @DisplayName("CN:01-02: Verify contract request, offer received, consumer counter-offer, provider terminated") public void cn_01_02() { negotiationMock.recordContractRequestedAction(ProviderActions::postOffer); negotiationMock.recordContractRequestedAction(ProviderActions::terminate); negotiationPipeline - .expectOffer(offer -> clientConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) - .sendRequest(datasetId, offerId, datasetId) + .expectOffer(offer -> consumerConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) + .sendRequest(datasetId, offerId) .thenWaitForState(OFFERED) .expectTermination() - .sendCounterRequest() + .sendCounterRequest("CD123:ACN0102:456", "ACN0102") .thenWaitForState(TERMINATED) .execute(); @@ -88,7 +65,7 @@ public void cn_01_02() { } @MandatoryTest - @DisplayName("Verify contract request, offer received, consumer accepted, provider agreement, consumer verified, provider finalized") + @DisplayName("CN:01-03: Verify contract request, offer received, consumer accepted, provider agreement, consumer verified, provider finalized") public void cn_01_03() { negotiationMock.recordContractRequestedAction(ProviderActions::postOffer); @@ -96,13 +73,13 @@ public void cn_01_03() { negotiationMock.recordConsumerVerifyAction(ProviderActions::postProviderFinalized); negotiationPipeline - .expectOffer(offer -> clientConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) - .sendRequest(datasetId, offerId, datasetId) + .expectOffer(offer -> consumerConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) + .sendRequest(datasetId, offerId) .thenWaitForState(OFFERED) - .expectAgreement(agreement -> clientConnector.getConsumerNegotiationManager().handleAgreement(agreement)) + .expectAgreement(agreement -> consumerConnector.getConsumerNegotiationManager().handleAgreement(agreement)) .acceptLastOffer() .thenWaitForState(AGREED) - .expectFinalized(event -> clientConnector.getConsumerNegotiationManager().handleFinalized(event)) + .expectFinalized(event -> consumerConnector.getConsumerNegotiationManager().handleFinalized(event)) .sendConsumerVerify() .thenWaitForState(FINALIZED) .execute(); @@ -111,17 +88,17 @@ public void cn_01_03() { } @MandatoryTest - @DisplayName("Verify contract request, provider agreement, consumer verified, provider finalized") + @DisplayName("CN:01-04: Verify contract request, provider agreement, consumer verified, provider finalized") public void cn_01_04() { negotiationMock.recordContractRequestedAction(ProviderActions::postProviderAgreed); negotiationMock.recordConsumerVerifyAction(ProviderActions::postProviderFinalized); negotiationPipeline - .expectAgreement(agreement -> clientConnector.getConsumerNegotiationManager().handleAgreement(agreement)) - .sendRequest(datasetId, offerId, datasetId) + .expectAgreement(agreement -> consumerConnector.getConsumerNegotiationManager().handleAgreement(agreement)) + .sendRequest(datasetId, offerId) .thenWaitForState(AGREED) - .expectFinalized(event -> clientConnector.getConsumerNegotiationManager().handleFinalized(event)) + .expectFinalized(event -> consumerConnector.getConsumerNegotiationManager().handleFinalized(event)) .sendConsumerVerify() .thenWaitForState(FINALIZED) .execute(); diff --git a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider02Test.java b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider02Test.java new file mode 100644 index 0000000..ab89521 --- /dev/null +++ b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider02Test.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.dataspacetck.dsp.verification.cn; + +import org.eclipse.dataspacetck.core.api.system.MandatoryTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; + +import static org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation.State.AGREED; +import static org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation.State.OFFERED; +import static org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation.State.TERMINATED; +import static org.eclipse.dataspacetck.dsp.verification.cn.ProviderActions.pause; +import static org.eclipse.dataspacetck.dsp.verification.cn.ProviderActions.postOffer; +import static org.eclipse.dataspacetck.dsp.verification.cn.ProviderActions.terminate; + +@Tag("base-compliance") +@DisplayName("CN_02: Provider test scenarios") +public class ContractNegotiationProvider02Test extends AbstractContractNegotiationProviderTest { + + @MandatoryTest + @DisplayName("CN:02-01: Verify contract request, provider terminated") + public void cn_02_01() { + + negotiationMock.recordContractRequestedAction(ProviderActions::terminate); + + negotiationPipeline + .sendRequest(datasetId, offerId) + .expectTermination() + .thenWaitForState(TERMINATED) + .execute(); + + negotiationMock.verify(); + } + + @MandatoryTest + @DisplayName("CN:02-02: Verify contract request, consumer terminated") + public void cn_02_02() { + + negotiationPipeline + .sendRequest(datasetId, offerId) + .sendTermination() + .thenVerifyProviderState(TERMINATED) + .execute(); + + negotiationMock.verify(); + } + + @MandatoryTest + @DisplayName("CN:02-03: Verify contract request, provider agreement, consumer terminated") + public void cn_02_03() { + + negotiationMock.recordContractRequestedAction(ProviderActions::postProviderAgreed); + + negotiationPipeline + .expectAgreement(agreement -> consumerConnector.getConsumerNegotiationManager().handleAgreement(agreement)) + .sendRequest(datasetId, offerId) + .thenWaitForState(AGREED) + .sendTermination() + .thenVerifyProviderState(TERMINATED) + .execute(); + + negotiationMock.verify(); + } + + @MandatoryTest + @DisplayName("CN:02-04: Verify contract request, offer received, consumer terminated") + public void cn_02_04() { + + negotiationMock.recordContractRequestedAction(ProviderActions::postOffer); + + negotiationPipeline + .expectOffer(offer -> consumerConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) + .sendRequest(datasetId, offerId) + .thenWaitForState(OFFERED) + .sendTermination() + .thenVerifyProviderState(TERMINATED) + .execute(); + + negotiationMock.verify(); + } + + @MandatoryTest + @DisplayName("CN:02-05: Verify contract request, offer received, provider terminated") + public void cn_02_05() { + + negotiationMock.recordContractRequestedAction(negotiation -> { + postOffer(negotiation); + pause(); + terminate(negotiation); + }); + + negotiationPipeline + .expectOffer(offer -> consumerConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) + .sendRequest(datasetId, offerId) + .thenWaitForState(OFFERED) + .expectTermination() + .thenWaitForState(TERMINATED) + .execute(); + + negotiationMock.verify(); + } + + @MandatoryTest + @DisplayName("CN:02-06: Verify contract request, offer received, consumer accepted, provider terminated") + public void cn_02_06() { + + negotiationMock.recordContractRequestedAction(ProviderActions::postOffer); + negotiationMock.recordConsumerAgreedAction(ProviderActions::terminate); + + negotiationPipeline + .expectOffer(offer -> consumerConnector.getConsumerNegotiationManager().handleProviderOffer(offer)) + .sendRequest(datasetId, offerId) + .thenWaitForState(OFFERED) + .acceptLastOffer() + .expectTermination() + .thenWaitForState(TERMINATED) + .execute(); + + negotiationMock.verify(); + } + + @MandatoryTest + @DisplayName("CN:02-07: Verify contract request, provider agreement, consumer verified, provider terminated") + public void cn_02_07() { + + negotiationMock.recordContractRequestedAction(ProviderActions::postProviderAgreed); + negotiationMock.recordConsumerVerifyAction(ProviderActions::terminate); + + negotiationPipeline + .expectAgreement(agreement -> consumerConnector.getConsumerNegotiationManager().handleAgreement(agreement)) + .sendRequest(datasetId, offerId) + .thenWaitForState(AGREED) + .expectTermination() + .sendConsumerVerify() + .thenWaitForState(TERMINATED) + .execute(); + + negotiationMock.verify(); + } + + +} diff --git a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ProviderActions.java b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ProviderActions.java index ff6ed5f..855a48a 100644 --- a/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ProviderActions.java +++ b/dsp/dsp-contract-negotiation/src/main/java/org/eclipse/dataspacetck/dsp/verification/cn/ProviderActions.java @@ -39,7 +39,7 @@ public class ProviderActions { private static final String NEGOTIATION_FINALIZE_TEMPLATE = "%s/negotiations/%s/events"; public static void postOffer(ContractNegotiation negotiation) { - var contractOffer = createOffer(negotiation.getId(), negotiation.getCorrelationId(), randomUUID().toString()); + var contractOffer = createOffer(negotiation.getId(), negotiation.getCorrelationId(), randomUUID().toString(), randomUUID().toString()); negotiation.transition(OFFERED); try (var response = postJson(format(NEGOTIATION_OFFER_TEMPLATE, negotiation.getCallbackAddress(), negotiation.getCorrelationId()), contractOffer)) { @@ -71,6 +71,14 @@ public static void terminate(ContractNegotiation negotiation) { } } + public static void pause() { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + private static void checkResponse(Response response) { if (!response.isSuccessful()) { throw new AssertionError("Unexpected response code: " + response.code()); diff --git a/dsp/dsp-contract-negotiation/src/test/java/org/eclipse/dataspacetck/dsp/verification/cn/DspContractNegotiationMandatory01TestTest.java b/dsp/dsp-contract-negotiation/src/test/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider01TestTest.java similarity index 82% rename from dsp/dsp-contract-negotiation/src/test/java/org/eclipse/dataspacetck/dsp/verification/cn/DspContractNegotiationMandatory01TestTest.java rename to dsp/dsp-contract-negotiation/src/test/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider01TestTest.java index 407e857..5d6670c 100644 --- a/dsp/dsp-contract-negotiation/src/test/java/org/eclipse/dataspacetck/dsp/verification/cn/DspContractNegotiationMandatory01TestTest.java +++ b/dsp/dsp-contract-negotiation/src/test/java/org/eclipse/dataspacetck/dsp/verification/cn/ContractNegotiationProvider01TestTest.java @@ -15,7 +15,5 @@ package org.eclipse.dataspacetck.dsp.verification.cn; -public class DspContractNegotiationMandatory01TestTest extends DspContractNegotiationMandatory01Test { - - +public class ContractNegotiationProvider01TestTest extends ContractNegotiationProvider01Test { } diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/DspSystemLauncher.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/DspSystemLauncher.java index e1a413f..522bc1d 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/DspSystemLauncher.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/DspSystemLauncher.java @@ -16,6 +16,7 @@ package org.eclipse.dataspacetck.dsp.system; import org.eclipse.dataspacetck.core.api.system.CallbackEndpoint; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; import org.eclipse.dataspacetck.core.spi.system.ServiceConfiguration; import org.eclipse.dataspacetck.core.spi.system.ServiceResolver; import org.eclipse.dataspacetck.core.spi.system.SystemConfiguration; @@ -35,19 +36,24 @@ import java.util.concurrent.ExecutorService; import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.eclipse.dataspacetck.core.api.system.SystemsConstants.TCK_PREFIX; /** - * Instantiates and bootstraps an DSP test fixture. + * Instantiates and bootstraps a DSP test fixture. */ @SuppressWarnings("unused") public class DspSystemLauncher implements SystemLauncher { - private static final String LOCAL_CONNECTOR_CONFIG = "dataspacetck.dsp.local.connector"; - private static final String CONNECTOR_BASE_URL_CONFIG = "dataspacetck.dsp.connector.http.url"; - private static final String THREAD_POOL_CONFIG = "dataspacetck.dsp.thread.pool"; + private static final String LOCAL_CONNECTOR_CONFIG = TCK_PREFIX + ".dsp.local.connector"; + private static final String CONNECTOR_BASE_URL_CONFIG = TCK_PREFIX + ".dsp.connector.http.url"; + private static final String THREAD_POOL_CONFIG = TCK_PREFIX + ".dsp.thread.pool"; + private static final String DEFAULT_WAIT_CONFIG = TCK_PREFIX + ".dsp.default.wait"; + private static final int DEFAULT_WAIT_SECONDS = 15; + private Monitor monitor; private ExecutorService executor; private String baseConnectorUrl; private boolean useLocalConnector; + private long waitTime = DEFAULT_WAIT_SECONDS; private Map clientConnectors = new ConcurrentHashMap<>(); private Map providerConnectors = new ConcurrentHashMap<>(); @@ -57,6 +63,8 @@ public class DspSystemLauncher implements SystemLauncher { @Override public void start(SystemConfiguration configuration) { + this.monitor = configuration.getMonitor(); + waitTime = configuration.getPropertyAsLong(DEFAULT_WAIT_CONFIG, DEFAULT_WAIT_SECONDS); executor = newFixedThreadPool(configuration.getPropertyAsInt(THREAD_POOL_CONFIG, 10)); useLocalConnector = configuration.getPropertyAsBoolean(LOCAL_CONNECTOR_CONFIG, false); if (!useLocalConnector) { @@ -94,14 +102,15 @@ private T createPipeline(Class type, ServiceConfiguration configuration, var scopeId = configuration.getScopeId(); var negotiationClient = createNegotiationClient(scopeId); var callbackEndpoint = (CallbackEndpoint) resolver.resolve(CallbackEndpoint.class, configuration); - var consumerConnector = clientConnectors.computeIfAbsent(scopeId, k -> new Connector()); - return type.cast(NegotiationPipeline.negotiationPipeline(negotiationClient, callbackEndpoint, consumerConnector)); + var consumerConnector = clientConnectors.computeIfAbsent(scopeId, k -> new Connector(monitor)); + var pipeline = new NegotiationPipeline(negotiationClient, callbackEndpoint, consumerConnector, monitor, waitTime); + return type.cast(pipeline); } private T createNegotiationMock(Class type, String scopeId) { return type.cast(negotiationMocks.computeIfAbsent(scopeId, k -> { if (useLocalConnector) { - var connector = providerConnectors.computeIfAbsent(scopeId, k2 -> new Connector()); + var connector = providerConnectors.computeIfAbsent(scopeId, k2 -> new Connector(monitor)); return new NegotiationProviderMockImpl(connector.getProviderNegotiationManager(), executor); } else { return new NoOpNegotiationProviderMock(); @@ -112,17 +121,17 @@ private T createNegotiationMock(Class type, String scopeId) { private T createConnector(Class type, ServiceConfiguration configuration) { var scopeId = configuration.getScopeId(); if (configuration.getAnnotations().stream().anyMatch(a -> a.annotationType().equals(Consumer.class))) { - return type.cast(clientConnectors.computeIfAbsent(scopeId, k -> new Connector())); + return type.cast(clientConnectors.computeIfAbsent(scopeId, k -> new Connector(monitor))); } - return type.cast(providerConnectors.computeIfAbsent(scopeId, k -> new Connector())); + return type.cast(providerConnectors.computeIfAbsent(scopeId, k -> new Connector(monitor))); } private NegotiationClient createNegotiationClient(String scopeId) { return negotiationClients.computeIfAbsent(scopeId, k -> { if (useLocalConnector) { - return new NegotiationClientImpl(providerConnectors.computeIfAbsent(scopeId, k2 -> new Connector())); + return new NegotiationClientImpl(providerConnectors.computeIfAbsent(scopeId, k2 -> new Connector(monitor)), monitor); } - return new NegotiationClientImpl(baseConnectorUrl); + return new NegotiationClientImpl(baseConnectorUrl, monitor); }); } diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/client/NegotiationClient.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/client/NegotiationClient.java index 7ac8f4f..795fd60 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/client/NegotiationClient.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/client/NegotiationClient.java @@ -30,7 +30,7 @@ public interface NegotiationClient { /** * Accepts the most recent offer. */ - void consumerAgree(Map offer); + void consumerAccept(Map offer); /** * Verifies the contract agreement with the provider. diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/Connector.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/Connector.java index e3c73bf..216d289 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/Connector.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/Connector.java @@ -15,6 +15,8 @@ package org.eclipse.dataspacetck.dsp.system.api.connector; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; + /** * Implements a simple, in-memory connector that supports control-plane operations for testing. */ @@ -30,8 +32,8 @@ public ConsumerNegotiationManager getConsumerNegotiationManager() { return consumerNegotiationManager; } - public Connector() { - consumerNegotiationManager = new ConsumerNegotiationManager(); + public Connector(Monitor monitor) { + consumerNegotiationManager = new ConsumerNegotiationManager(monitor); providerNegotiationManager = new ProviderNegotiationManager(); } } diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ConsumerNegotiationManager.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ConsumerNegotiationManager.java index 7af9841..2017fc5 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ConsumerNegotiationManager.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ConsumerNegotiationManager.java @@ -15,6 +15,7 @@ package org.eclipse.dataspacetck.dsp.system.api.connector; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; import org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation; import org.jetbrains.annotations.NotNull; @@ -24,56 +25,62 @@ import java.util.concurrent.ConcurrentLinkedQueue; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_CONSUMER_PID_EXPANDED; +import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_PROVIDER_PID_EXPANDED; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createOfferAck; -import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringProperty; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringIdProperty; /** * Manages contract negotiations on a consumer. */ public class ConsumerNegotiationManager { + private Monitor monitor; private Map negotiations = new ConcurrentHashMap<>(); private Queue listeners = new ConcurrentLinkedQueue<>(); + public ConsumerNegotiationManager(Monitor monitor) { + this.monitor = monitor; + } + /** * Called after a contract has been requested and the negotiation id is returned by the provider. The provider negotiation id will be set as the correlation id * on the consumer. */ - public void contractRequested(String processId, String correlationId) { - var contractNegotiation = getNegotiations().get(processId); - contractNegotiation.setCorrelationId(correlationId, ContractNegotiation.State.REQUESTED); + public void contractRequested(String consumerId, String providerId) { + var contractNegotiation = getNegotiations().get(consumerId); + contractNegotiation.setCorrelationId(providerId, ContractNegotiation.State.REQUESTED); } /** * Transitions the negotiation to {@link ContractNegotiation.State#REQUESTED} when a counter-offer has been made. */ - public void counterOffer(String processId) { - var contractNegotiation = getNegotiations().get(processId); + public void counterOffer(String consumerId) { + var contractNegotiation = getNegotiations().get(consumerId); contractNegotiation.transition(ContractNegotiation.State.REQUESTED); } /** * Transitions the negotiation to {@link ContractNegotiation.State#ACCEPTED} when an offer is accepted by the consumer. */ - public void agree(String processId) { - var contractNegotiation = getNegotiations().get(processId); + public void agree(String consumerId) { + var contractNegotiation = getNegotiations().get(consumerId); contractNegotiation.transition(ContractNegotiation.State.ACCEPTED); } /** * Transitions the negotiation to {@link ContractNegotiation.State#VERIFIED} when a verification is being sent. */ - public void verify(String processId) { - var contractNegotiation = getNegotiations().get(processId); + public void verify(String consumerId) { + var contractNegotiation = getNegotiations().get(consumerId); contractNegotiation.transition(ContractNegotiation.State.VERIFIED); } /** * Transitions the negotiation to {@link ContractNegotiation.State#TERMINATED}. */ - public void terminate(String processId) { - var negotiation = getNegotiations().get(processId); + public void terminate(String consumerId) { + var negotiation = getNegotiations().get(consumerId); negotiation.transition(ContractNegotiation.State.TERMINATED, n -> listeners.forEach(l -> l.terminated(n))); } @@ -93,8 +100,10 @@ public ContractNegotiation createNegotiation(String datasetId) { * Processes an offer received from the provider. */ public Map handleProviderOffer(Map offer) { - var id = stringProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, offer); - var negotiation = findById(id); + var providerId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, offer); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + monitor.debug("Received provider offer: " + providerId); + var consumerId = stringIdProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, offer); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + var negotiation = findById(consumerId); negotiation.storeOffer(offer, ContractNegotiation.State.OFFERED); return createOfferAck(negotiation.getCorrelationId(), negotiation.getId(), ContractNegotiation.State.OFFERED); } @@ -103,8 +112,10 @@ public Map handleProviderOffer(Map offer) { * Processes an agreement received from the provider. */ public void handleAgreement(Map agreement) { - var id = stringProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, agreement); - var negotiation = findById(id); + var providerId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, agreement); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + monitor.debug("Received provider agreement: " + providerId); + var consumerId = stringIdProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, agreement); // // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + var negotiation = findById(consumerId); negotiation.storeAgreement(agreement); } @@ -112,8 +123,10 @@ public void handleAgreement(Map agreement) { * Processes a finalize event received from the provider. */ public void handleFinalized(Map event) { - var id = stringProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, event); - var negotiation = findById(id); + var providerId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, event); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + monitor.debug("Received provider finalize: " + providerId); + var consumerId = stringIdProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, event); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + var negotiation = findById(consumerId); negotiation.transition(ContractNegotiation.State.FINALIZED); } diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ProviderNegotiationManager.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ProviderNegotiationManager.java index b8267c8..1daf2ff 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ProviderNegotiationManager.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/connector/ProviderNegotiationManager.java @@ -30,7 +30,9 @@ import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_OFFER_EXPANDED; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_PROVIDER_PID_EXPANDED; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.ID; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.compactStringProperty; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.mapProperty; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringIdProperty; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringProperty; /** @@ -45,10 +47,9 @@ public class ProviderNegotiationManager { * Called when a contract request is received. */ public ContractNegotiation handleContractRequest(Map contractRequest) { - var processId = (String) contractRequest.get(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED); - - if (processId != null) { + if (contractRequest.containsKey(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED)) { // the message is a counter-offer + var processId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, contractRequest); return handleCounterOffer(contractRequest, processId); } else { // the message is an initial request @@ -68,7 +69,7 @@ public void handleConsumerVerified(String processId, Map verific } public void terminate(Map termination) { - var processId = requireNonNull(stringProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, termination)); + var processId = requireNonNull(stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, termination)); var negotiation = negotiations.get(processId); negotiation.transition(ContractNegotiation.State.TERMINATED, n -> listeners.forEach(l -> l.terminated(n))); } @@ -83,7 +84,7 @@ private ContractNegotiation handleCounterOffer(Map contractReque @NotNull private ContractNegotiation handleInitialRequest(Map contractRequest) { - var consumerId = stringProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, contractRequest); + var consumerId = stringIdProperty(DSPACE_PROPERTY_CONSUMER_PID_EXPANDED, contractRequest); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 var previousNegotiation = findByCorrelationId(consumerId); if (previousNegotiation != null) { return previousNegotiation; @@ -91,7 +92,7 @@ private ContractNegotiation handleInitialRequest(Map contractReq var offer = mapProperty(DSPACE_PROPERTY_OFFER_EXPANDED, contractRequest); - var offerId = stringProperty(ID, offer); + var offerId = compactStringProperty(ID, offer); var callbackAddress = stringProperty(DSPACE_PROPERTY_CALLBACK_ADDRESS_EXPANDED, contractRequest); var builder = ContractNegotiation.Builder.newInstance() diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/DspConstants.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/DspConstants.java index 2b8d8fa..ea5bab0 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/DspConstants.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/DspConstants.java @@ -26,6 +26,8 @@ public interface DspConstants { String ID = "@id"; + String VALUE = "@value"; + String TYPE = "@type"; String DSPACE_NAMESPACE_KEY = "dspace"; diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/MessageFunctions.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/MessageFunctions.java index e36751d..bb64098 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/MessageFunctions.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/message/MessageFunctions.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.UUID; +import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -41,6 +42,7 @@ import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_STATE; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.ID; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.TYPE; +import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.VALUE; import static org.eclipse.dataspacetck.dsp.system.api.message.OdrlConstants.ODRL_AGREEMENT_TYPE; import static org.eclipse.dataspacetck.dsp.system.api.message.OdrlConstants.ODRL_NAMESPACE; import static org.eclipse.dataspacetck.dsp.system.api.message.OdrlConstants.ODRL_NAMESPACE_KEY; @@ -55,10 +57,11 @@ * Utility methods for creating DSP messages. */ public class MessageFunctions { + private static final Map IDENTITY_TYPE = Map.of("@type", "@id"); public static Map createContractRequest(String consumerPid, String offerId, String targetId, String callbackAddress) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractRequestMessage"); - message.put(CONTEXT, createContext()); + message.put(CONTEXT, createDspContext()); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerPid); var offer = new LinkedHashMap(); @@ -73,20 +76,25 @@ public static Map createContractRequest(String consumerPid, Stri return message; } - public static Map createCounterOffer(String providerId, String consumerId) { + public static Map createCounterOffer(String providerId, + String consumerId, + String offerId, + String targetId, + String callbackAddress) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractRequestMessage"); // do NOT override id + message.put(CONTEXT, createDspContext()); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerId); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerId); - message.put(CONTEXT, createContext()); - message.put(DSPACE_PROPERTY_OFFER, createOffer(providerId, consumerId, UUID.randomUUID().toString())); + message.put(DSPACE_PROPERTY_OFFER, createOfferPolicy(offerId, targetId)); + message.put(DSPACE_PROPERTY_CALLBACK_ADDRESS, callbackAddress); return message; } public static Map createTermination(String providerId, String consumerId, String code, String... reasons) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractNegotiationTerminationMessage"); - message.put(CONTEXT, createContext()); + message.put(CONTEXT, createDspContext()); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerId); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerId); @@ -99,52 +107,60 @@ public static Map createTermination(String providerId, String co } public static Map createAcceptedEvent(String processId, String consumerId) { - return createEvent(processId, consumerId, "accepted"); + return createEvent(processId, consumerId, "ACCEPTED"); } public static Map createFinalizedEvent(String processId, String consumerId) { - return createEvent(processId, consumerId, "finalized"); + return createEvent(processId, consumerId, "FINALIZED"); } public static Map createEvent(String providerId, String consumerId, String eventType) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractNegotiationEventMessage"); - message.put(CONTEXT, createContext()); + message.put(CONTEXT, createDspContext()); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerId); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerId); - message.put(DSPACE_PROPERTY_EVENT_TYPE, eventType); + message.put(DSPACE_PROPERTY_EVENT_TYPE, DSPACE_NAMESPACE + eventType); return message; } - public static Map createVerification(String providerId) { + public static Map createVerification(String providerId, String consumerId) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractAgreementVerificationMessage"); - message.put(CONTEXT, createContext()); + message.put(CONTEXT, createDspContext()); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerId); + message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerId); return message; } - public static Map createOffer(String providerId, String consumerId, String offerId) { + public static Map createOffer(String providerId, String consumerId, String offerId, String targetId) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractOfferMessage"); - var context = createContext(); + var context = createDspContext(); message.put(CONTEXT, context); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerId); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerId); + var offer = createOfferPolicy(offerId, targetId); + + message.put(DSPACE_PROPERTY_OFFER, offer); + + return message; + } + + @NotNull + private static LinkedHashMap createOfferPolicy(String offerId, String targetId) { var offer = new LinkedHashMap(); offer.put(TYPE, ODRL_OFFER_TYPE); // WORKAROUND: REMOVE @type offer.put(ID, offerId); var permissions = Map.of(ODRL_PROPERTY_ACTION, ODRL_USE, ODRL_PROPERTY_CONSTRAINTS, emptyList()); offer.put(ODRL_PROPERTY_PERMISSION, List.of(permissions)); - - message.put(DSPACE_PROPERTY_OFFER, offer); - - return message; + offer.put(ODRL_PROPERTY_TARGET, targetId); + return offer; } public static Map createAgreement(String providerId, String consumerId, String agreementId, String target) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractAgreementMessage"); - var context = createContext(); + var context = createDspContext(); message.put(CONTEXT, context); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerId); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerId); @@ -163,7 +179,7 @@ public static Map createAgreement(String providerId, String cons public static Map createNegotiationResponse(String providerPid, String consumerPid, String state) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractNegotiation"); - var context = createContext(); + var context = createDspContext(); message.put(CONTEXT, context); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerPid); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerPid); @@ -173,7 +189,7 @@ public static Map createNegotiationResponse(String providerPid, public static Map createOfferAck(String providerId, String consumerId, State state) { var message = createBaseMessage(DSPACE_NAMESPACE_PREFIX + "ContractNegotiation"); - var context = createContext(); + var context = createDspContext(); message.put(CONTEXT, context); message.put(DSPACE_PROPERTY_PROVIDER_PID, providerId); message.put(DSPACE_PROPERTY_CONSUMER_PID, consumerId); @@ -182,33 +198,82 @@ public static Map createOfferAck(String providerId, String consu } public static Map mapProperty(String key, Map map) { - var value = requireNonNull(map.get(key), "No value for: " + key); - //noinspection unchecked - return (Map) value; + var untypedValue = requireNonNull(map.get(key), "No value for: " + key); + //noinspection rawtypes + if (untypedValue instanceof List valueList) { + if (valueList.isEmpty()) { + throw new AssertionError(format("Property '%s' was empty", key)); + } + @SuppressWarnings("SequencedCollectionMethodCanBeUsed") + var valueContainer = valueList.get(0); + if (valueContainer instanceof Map) { + return map; + } + } + throw new AssertionError(format("Property '%s' is not a Map", key)); } - public static String stringProperty(String key, Map map) { + public static String compactStringProperty(String key, Map map) { var value = requireNonNull(map.get(key), "No value for: " + key); return (String) value; } - @NotNull - private static Map createBaseMessage(String type) { - var message = new LinkedHashMap(); - message.put(ID, UUID.randomUUID().toString()); - message.put(TYPE, type); - return message; + public static String stringProperty(String key, Map map) { + return stringProperty(key, VALUE, map); + } + + public static String stringIdProperty(String key, Map map) { + return stringProperty(key, ID, map); + } + + public static String stringProperty(String key, String valKey, Map map) { + var untypedValue = requireNonNull(map.get(key), "No value for: " + key); + //noinspection rawtypes + if (untypedValue instanceof List valueList) { + if (valueList.isEmpty()) { + throw new AssertionError(format("Property '%s' was empty", key)); + } + @SuppressWarnings("SequencedCollectionMethodCanBeUsed") + var valueContainer = valueList.get(0); + if (valueContainer instanceof Map) { + @SuppressWarnings("rawtypes") + var value = requireNonNull(((Map) valueContainer).get(valKey), format("No %s attribute for property: %s", valKey, key)); + return value.toString(); + } + } + throw new AssertionError(format("Property '%s' was not in expanded @value form", key)); + } + + public static String identityProperty(String key, Map map) { + var value = requireNonNull(map.get(key), "No value for: " + key); + if (value instanceof Map) { + @SuppressWarnings("rawtypes") + var idValue = requireNonNull(((Map) value).get(ID), "No @id value for property: " + key); + return idValue.toString(); + } + throw new AssertionError(format("Property '%s' was not in expanded @id form", key)); } - private static Map createContext() { + public static Map createDspContext() { var context = new LinkedHashMap(); context.put(DSPACE_NAMESPACE_KEY, DSPACE_NAMESPACE); context.put(ODRL_NAMESPACE_KEY, ODRL_NAMESPACE); context.put("target", "odrl:target"); - context.put("odrl:target", Map.of("@type", "@id")); + context.put("odrl:target", IDENTITY_TYPE); + context.put(DSPACE_NAMESPACE_PREFIX + "state", IDENTITY_TYPE); + context.put(DSPACE_NAMESPACE_PREFIX + "consumerPid", IDENTITY_TYPE); + context.put(DSPACE_NAMESPACE_PREFIX + "providerPid", IDENTITY_TYPE); return context; } + @NotNull + private static Map createBaseMessage(String type) { + var message = new LinkedHashMap(); + message.put(ID, UUID.randomUUID().toString()); + message.put(TYPE, type); + return message; + } + private MessageFunctions() { } diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/pipeline/NegotiationPipeline.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/pipeline/NegotiationPipeline.java index 144e5fd..e229eb9 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/pipeline/NegotiationPipeline.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/pipeline/NegotiationPipeline.java @@ -16,81 +16,116 @@ package org.eclipse.dataspacetck.dsp.system.api.pipeline; import org.awaitility.core.ConditionTimeoutException; +import org.eclipse.dataspacetck.core.api.message.MessageSerializer; import org.eclipse.dataspacetck.core.api.system.CallbackEndpoint; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; import org.eclipse.dataspacetck.dsp.system.api.client.NegotiationClient; import org.eclipse.dataspacetck.dsp.system.api.connector.Connector; import org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import java.util.function.Function; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; +import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_NAMESPACE; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_PROVIDER_PID_EXPANDED; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_STATE_EXPANDED; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createAcceptedEvent; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createContractRequest; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createCounterOffer; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createDspContext; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createTermination; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createVerification; -import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringProperty; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringIdProperty; +import static org.eclipse.dataspacetck.dsp.system.api.statemachine.ContractNegotiation.State.TERMINATED; import static org.junit.jupiter.api.Assertions.assertEquals; /** * A test pipeline to create and execute message interaction tests. */ public class NegotiationPipeline { - private static final int DEFAULT_WAIT_SECONDS = 15; + private static final CountDownLatch NO_WAIT_LATCH = new CountDownLatch(0); private static final String NEGOTIATIONS_OFFER_PATH = "/negotiations/[^/]+/offer/"; private static final String NEGOTIATIONS_AGREEMENT_PATH = "/negotiations/[^/]+/agreement"; private static final String NEGOTIATIONS_TERMINATION_PATH = "/negotiations/[^/]+/termination"; private static final String NEGOTIATION_EVENT_PATH = "/negotiations/[^/]+/events"; + private final long waitTime; + + private Monitor monitor; private CallbackEndpoint endpoint; private Connector connector; private NegotiationClient negotiationClient; - private long waitTime = DEFAULT_WAIT_SECONDS; private List stages = new ArrayList<>(); - private ContractNegotiation clientNegotiation; + private ContractNegotiation negotiation; - public static NegotiationPipeline negotiationPipeline(NegotiationClient negotiationClient, CallbackEndpoint endpoint, Connector connector) { - var pipeline = new NegotiationPipeline(); - pipeline.negotiationClient = negotiationClient; - pipeline.connector = connector; - pipeline.endpoint = endpoint; - return pipeline; - } + /* + Used by {@link #thenWait} methods to synchronize with the completion of a recorded expectXXX(..) method to avoid message interleaving. + For example given: - @SuppressWarnings("unused") - public NegotiationPipeline waitTime(long waitTime) { +

+         .expectOffer(offer -> consumerConnector.getConsumerNegotiationManager().handleProviderOffer(offer))
+         .sendRequest(datasetId, offerId)
+         .thenWaitForState(OFFERED)
+         .expectAgreement(agreement -> consumerConnector.getConsumerNegotiationManager().handleAgreement(agreement))
+     
+ + When sendRequest is executed and the pipeline waits for the OFFERED state, it must ensure that after OFFERED is set the + expectOffer() runnable has completed before executing the next step, expectAgreement(). Otherwise, the send agreement message + could arrive at the provider before the expectOffer() runnable is completed, which closes the HTTP socket the provider opened to + send the offer. + + Every expectXXXX(..) method places a latch on the deque which is then popped by the subsequent {@link #thenWait} method. + The {@link #thenWait} method waits on the latch, which is released after the expectXXXX(..) method completes. + */ + private Deque expectLatches = new ArrayDeque<>(); + + public NegotiationPipeline(NegotiationClient negotiationClient, + CallbackEndpoint endpoint, + Connector connector, + Monitor monitor, + long waitTime) { + this.negotiationClient = negotiationClient; + this.connector = connector; + this.endpoint = endpoint; + this.monitor = monitor; this.waitTime = waitTime; - return this; } - public NegotiationPipeline sendRequest(String datasetId, String offerId, String targetId) { + @SuppressWarnings("unused") + public NegotiationPipeline sendRequest(String datasetId, String offerId) { stages.add(() -> { - clientNegotiation = connector.getConsumerNegotiationManager().createNegotiation(datasetId); + negotiation = connector.getConsumerNegotiationManager().createNegotiation(datasetId); - var contractRequest = createContractRequest(clientNegotiation.getId(), offerId, targetId, endpoint.getAddress()); + var contractRequest = createContractRequest(negotiation.getId(), offerId, datasetId, endpoint.getAddress()); + monitor.debug("Sending contract request"); var response = negotiationClient.contractRequest(contractRequest); - var correlationId = stringProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, response); - connector.getConsumerNegotiationManager().contractRequested(clientNegotiation.getId(), correlationId); + var correlationId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, response); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + connector.getConsumerNegotiationManager().contractRequested(negotiation.getId(), correlationId); }); return this; } - public NegotiationPipeline sendCounterRequest() { + public NegotiationPipeline sendCounterRequest(String offerId, String targetId) { stages.add(() -> { - var contractRequest = createCounterOffer(clientNegotiation.getCorrelationId(), clientNegotiation.getId()); - connector.getConsumerNegotiationManager().counterOffer(clientNegotiation.getId()); + var providerId = negotiation.getCorrelationId(); + var consumerId = negotiation.getId(); + var contractRequest = createCounterOffer(providerId, consumerId, offerId, targetId, endpoint.getAddress()); + + monitor.debug("Sending counter offer: " + providerId); + connector.getConsumerNegotiationManager().counterOffer(consumerId); negotiationClient.contractRequest(contractRequest); }); return this; @@ -98,25 +133,37 @@ public NegotiationPipeline sendCounterRequest() { public NegotiationPipeline sendTermination() { stages.add(() -> { - var termination = createTermination(clientNegotiation.getCorrelationId(), clientNegotiation.getId(), "1"); + pause(); + var providerId = negotiation.getCorrelationId(); + var consumerId = negotiation.getId(); + var termination = createTermination(providerId, consumerId, "1"); + + monitor.debug("Sending termination: " + providerId); negotiationClient.terminate(termination); - connector.getConsumerNegotiationManager().terminate(clientNegotiation.getId()); + connector.getConsumerNegotiationManager().terminate(consumerId); }); return this; } public NegotiationPipeline acceptLastOffer() { stages.add(() -> { - connector.getConsumerNegotiationManager().agree(clientNegotiation.getId()); - negotiationClient.consumerAgree(createAcceptedEvent(clientNegotiation.getCorrelationId(), clientNegotiation.getId())); + var providerId = negotiation.getCorrelationId(); + var consumerId = negotiation.getId(); + monitor.debug("Accepting offer: " + providerId); + connector.getConsumerNegotiationManager().agree(consumerId); + negotiationClient.consumerAccept(createAcceptedEvent(providerId, consumerId)); }); return this; } public NegotiationPipeline sendConsumerVerify() { stages.add(() -> { - connector.getConsumerNegotiationManager().verify(clientNegotiation.getId()); - negotiationClient.consumerVerify(createVerification(clientNegotiation.getCorrelationId())); + pause(); + var providerId = negotiation.getCorrelationId(); + var consumerId = negotiation.getId(); + monitor.debug("Sending verification: " + providerId); + connector.getConsumerNegotiationManager().verify(consumerId); + negotiationClient.consumerVerify(createVerification(providerId, consumerId)); }); return this; } @@ -127,61 +174,55 @@ public NegotiationPipeline then(Runnable runnable) { } public NegotiationPipeline thenWaitForState(ContractNegotiation.State state) { - return thenWait("state to transition to " + state, () -> state == clientNegotiation.getState()); + return thenWait("state to transition to " + state, () -> state == negotiation.getState()); } public NegotiationPipeline thenWait(String description, Callable condition) { + var latch = expectLatches.isEmpty() ? NO_WAIT_LATCH : expectLatches.pop(); stages.add(() -> { try { + try { + if (!latch.await(waitTime, SECONDS)) { + throw new RuntimeException("Timeout waiting for " + description); + } + } catch (InterruptedException e) { + Thread.interrupted(); + throw new RuntimeException("Interrupted while waiting for " + description, e); + } await().atMost(waitTime, SECONDS).until(condition); + monitor.debug("Done waiting for " + description); } catch (ConditionTimeoutException e) { throw new AssertionError("Timeout waiting for " + description); + } catch (Exception e) { + throw new RuntimeException(e); } }); return this; } public NegotiationPipeline expectOffer(Function, Map> action) { + var latch = new CountDownLatch(1); + expectLatches.add(latch); stages.add(() -> endpoint.registerHandler(NEGOTIATIONS_OFFER_PATH, offer -> { - //noinspection unchecked - var negotiation = action.apply((Map) offer); + var negotiation = action.apply((MessageSerializer.processJsonLd(offer, createDspContext()))); endpoint.deregisterHandler(NEGOTIATIONS_OFFER_PATH); - return negotiation; + latch.countDown(); + return MessageSerializer.serialize(negotiation); })); return this; } public NegotiationPipeline expectAgreement(Consumer> action) { - stages.add(() -> - endpoint.registerHandler(NEGOTIATIONS_AGREEMENT_PATH, agreement -> { - //noinspection unchecked - action.accept((Map) agreement); - endpoint.deregisterHandler(NEGOTIATIONS_AGREEMENT_PATH); - return null; - })); - return this; + return addHandlerAction(NEGOTIATIONS_AGREEMENT_PATH, action); } public NegotiationPipeline expectFinalized(Consumer> action) { - stages.add(() -> - endpoint.registerHandler(NEGOTIATION_EVENT_PATH, agreement -> { - //noinspection unchecked - action.accept((Map) agreement); - endpoint.deregisterHandler(NEGOTIATION_EVENT_PATH); - return null; - })); - return this; + return addHandlerAction(NEGOTIATION_EVENT_PATH, action); } public NegotiationPipeline expectTermination() { - stages.add(() -> - endpoint.registerHandler(NEGOTIATIONS_TERMINATION_PATH, termination -> { - clientNegotiation.transition(ContractNegotiation.State.TERMINATED); - endpoint.deregisterHandler(NEGOTIATIONS_TERMINATION_PATH); - return null; - })); - return this; + return addHandlerAction(NEGOTIATIONS_TERMINATION_PATH, d -> negotiation.transition(TERMINATED)); } @SuppressWarnings("unused") @@ -191,20 +232,21 @@ public NegotiationPipeline thenVerify(Runnable runnable) { @SuppressWarnings("unused") public NegotiationPipeline thenVerifyNegotiation(Consumer consumer) { - return then(() -> consumer.accept(clientNegotiation)); + return then(() -> consumer.accept(negotiation)); } @SuppressWarnings("unused") public NegotiationPipeline thenVerifyState(ContractNegotiation.State state) { - stages.add(() -> assertEquals(state, clientNegotiation.getState())); + stages.add(() -> assertEquals(state, negotiation.getState())); return this; } public NegotiationPipeline thenVerifyProviderState(ContractNegotiation.State state) { stages.add(() -> { - var providerNegotiation = negotiationClient.getNegotiation(clientNegotiation.getCorrelationId()); - var actual = stringProperty(DSPACE_PROPERTY_STATE_EXPANDED, providerNegotiation); - assertEquals(state.toString(), actual); + pause(); + var providerNegotiation = negotiationClient.getNegotiation(negotiation.getCorrelationId()); + var actual = stringIdProperty(DSPACE_PROPERTY_STATE_EXPANDED, providerNegotiation); + assertEquals(DSPACE_NAMESPACE + state.toString(), actual); }); return this; } @@ -213,4 +255,25 @@ public void execute() { stages.forEach(Runnable::run); } + private NegotiationPipeline addHandlerAction(String path, Consumer> action) { + var latch = new CountDownLatch(1); + expectLatches.add(latch); + stages.add(() -> + endpoint.registerHandler(path, agreement -> { + action.accept((MessageSerializer.processJsonLd(agreement, createDspContext()))); + endpoint.deregisterHandler(path); + latch.countDown(); + return null; + })); + return this; + } + + private void pause() { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/statemachine/ContractNegotiation.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/statemachine/ContractNegotiation.java index f96f3e2..8060863 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/statemachine/ContractNegotiation.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/api/statemachine/ContractNegotiation.java @@ -129,8 +129,8 @@ public void storeOffer(Map offer, State state) { */ public void storeOffer(Map offer, State state, Consumer work) { lockManager.writeLock(() -> { - transition(state); offers.add(offer); + transition(state); return null; }); work.accept(this); @@ -148,8 +148,8 @@ public void storeAgreement(Map agreement) { */ public void storeAgreement(Map agreement, Consumer work) { lockManager.writeLock(() -> { - transition(AGREED); this.agreement = agreement; + transition(AGREED); return null; }); work.accept(this); diff --git a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/client/NegotiationClientImpl.java b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/client/NegotiationClientImpl.java index a1bdd8b..93fe15f 100644 --- a/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/client/NegotiationClientImpl.java +++ b/dsp/dsp-system/src/main/java/org/eclipse/dataspacetck/dsp/system/client/NegotiationClientImpl.java @@ -15,6 +15,7 @@ package org.eclipse.dataspacetck.dsp.system.client; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; import org.eclipse.dataspacetck.dsp.system.api.client.NegotiationClient; import org.eclipse.dataspacetck.dsp.system.api.connector.Connector; @@ -24,10 +25,14 @@ import static org.eclipse.dataspacetck.core.api.message.MessageSerializer.processJsonLd; import static org.eclipse.dataspacetck.dsp.system.api.http.HttpFunctions.getJson; import static org.eclipse.dataspacetck.dsp.system.api.http.HttpFunctions.postJson; +import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_NAMESPACE; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_PROVIDER_PID; import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_PROVIDER_PID_EXPANDED; +import static org.eclipse.dataspacetck.dsp.system.api.message.DspConstants.DSPACE_PROPERTY_STATE_EXPANDED; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.compactStringProperty; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createDspContext; import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.createNegotiationResponse; -import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringProperty; +import static org.eclipse.dataspacetck.dsp.system.api.message.MessageFunctions.stringIdProperty; /** * Default implementation that supports dispatch to a local, in-memory test connector or a remote connector system via HTTP. @@ -36,29 +41,35 @@ public class NegotiationClientImpl implements NegotiationClient { private static final String GET_PATH = "negotiations/%s"; private static final String REQUEST_PATH = "negotiations/request"; private static final String TERMINATE_PATH = "negotiations/%s/termination"; + private static final String EVENT_PATH = "negotiations/%s/events"; + private static final String VERIFICATION_PATH = "negotiations/%s/agreement/verification"; private String connectorBaseUrl; private Connector systemConnector; + private Monitor monitor; - public NegotiationClientImpl(String connectorBaseUrl) { + public NegotiationClientImpl(String connectorBaseUrl, Monitor monitor) { this.connectorBaseUrl = connectorBaseUrl.endsWith("/") ? connectorBaseUrl : connectorBaseUrl + "/"; + this.monitor = monitor; } - public NegotiationClientImpl(Connector systemConnector) { + public NegotiationClientImpl(Connector systemConnector, Monitor monitor) { this.systemConnector = systemConnector; + this.monitor = monitor; } @Override public Map contractRequest(Map contractRequest) { if (systemConnector != null) { - var compacted = processJsonLd(contractRequest); + var compacted = processJsonLd(contractRequest, createDspContext()); var negotiation = systemConnector.getProviderNegotiationManager().handleContractRequest(compacted); return processJsonLd(createNegotiationResponse(negotiation.getId(), negotiation.getCorrelationId(), - negotiation.getState().toString().toLowerCase())); + negotiation.getState().toString().toLowerCase()), createDspContext()); } else { try (var response = postJson(connectorBaseUrl + REQUEST_PATH, contractRequest)) { + monitor.debug("Received contract request response"); //noinspection DataFlowIssue - return processJsonLd(response.body().byteStream()); + return processJsonLd(response.body().byteStream(), createDspContext()); } } } @@ -66,14 +77,15 @@ public Map contractRequest(Map contractRequest) @Override public void terminate(Map termination) { if (systemConnector != null) { - var compacted = processJsonLd(termination); + var compacted = processJsonLd(termination, createDspContext()); systemConnector.getProviderNegotiationManager().terminate(compacted); } else { - var processId = stringProperty(DSPACE_PROPERTY_PROVIDER_PID, termination); - try (var response = postJson(connectorBaseUrl + format(TERMINATE_PATH, processId), termination)) { + var providerId = compactStringProperty(DSPACE_PROPERTY_PROVIDER_PID, termination); + try (var response = postJson(connectorBaseUrl + format(TERMINATE_PATH, providerId), termination)) { if (!response.isSuccessful()) { - throw new AssertionError("Terminate request failed with code: " + response.code()); + throw new AssertionError(format("Terminate request failed with code %s: %s", response.code(), providerId)); } + monitor.debug("Received negotiation terminate response: " + providerId); } } } @@ -82,35 +94,55 @@ public void terminate(Map termination) { public Map getNegotiation(String providerPid) { if (systemConnector != null) { var negotiation = systemConnector.getProviderNegotiationManager().findById(providerPid); - return processJsonLd(createNegotiationResponse(negotiation.getId(), - negotiation.getCorrelationId(), - negotiation.getState().toString())); + var consumerPid = negotiation.getCorrelationId(); + var state = DSPACE_NAMESPACE + negotiation.getState().toString(); + return processJsonLd(createNegotiationResponse(providerPid, consumerPid, state), createDspContext()); } else { try (var response = getJson(connectorBaseUrl + format(GET_PATH, providerPid))) { //noinspection DataFlowIssue - return processJsonLd(response.body().byteStream()); + var jsonResponse = processJsonLd(response.body().byteStream(), createDspContext()); + var providerId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, jsonResponse); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + var state = stringIdProperty(DSPACE_PROPERTY_STATE_EXPANDED, jsonResponse); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + monitor.debug(format("Received negotiation status response with state %s: %s", state, providerId)); + return jsonResponse; } } } @Override - public void consumerAgree(Map event) { + public void consumerAccept(Map event) { if (systemConnector != null) { - var compacted = processJsonLd(event); - var processId = stringProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, compacted); - systemConnector.getProviderNegotiationManager().handleConsumerAgreed(processId); + var compacted = processJsonLd(event, createDspContext()); + var providerId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, compacted); // // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + systemConnector.getProviderNegotiationManager().handleConsumerAgreed(providerId); + } else { + var providerId = compactStringProperty(DSPACE_PROPERTY_PROVIDER_PID, event); + try (var response = postJson(connectorBaseUrl + format(EVENT_PATH, providerId), event)) { + if (!response.isSuccessful()) { + throw new AssertionError(format("Accept event failed with code %s: %s ", response.code(), providerId)); + } + monitor.debug("Received accept response: " + providerId); + } + } - // TODO implement HTTP invoke } @Override public void consumerVerify(Map verification) { if (systemConnector != null) { - var compacted = processJsonLd(verification); - var processId = stringProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, compacted); - systemConnector.getProviderNegotiationManager().handleConsumerVerified(processId, verification); + var compacted = processJsonLd(verification, createDspContext()); + var providerId = stringIdProperty(DSPACE_PROPERTY_PROVIDER_PID_EXPANDED, compacted); // FIXME https://github.com/eclipse-dataspacetck/cvf/issues/92 + systemConnector.getProviderNegotiationManager().handleConsumerVerified(providerId, verification); + } else { + var providerId = compactStringProperty(DSPACE_PROPERTY_PROVIDER_PID, verification); + try (var response = postJson(connectorBaseUrl + format(VERIFICATION_PATH, providerId), verification)) { + if (!response.isSuccessful()) { + throw new AssertionError("Verification event failed with code: " + response.code()); + } + monitor.debug("Received verification response: " + providerId); + } + } - // TODO implement HTTP invoke } diff --git a/runtimes/dsp-tck/src/main/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuite.java b/runtimes/dsp-tck/src/main/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuite.java index 834d6a6..f857942 100644 --- a/runtimes/dsp-tck/src/main/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuite.java +++ b/runtimes/dsp-tck/src/main/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuite.java @@ -14,25 +14,73 @@ package org.eclipse.dataspacetck.dsp.suite; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; +import org.eclipse.dataspacetck.core.system.ConsoleMonitor; import org.eclipse.dataspacetck.runtime.ConsoleResultWriter; import org.eclipse.dataspacetck.runtime.TckRuntime; +import org.jetbrains.annotations.NotNull; + +import java.io.FileReader; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +import static java.lang.Boolean.parseBoolean; +import static org.eclipse.dataspacetck.core.api.system.SystemsConstants.TCK_LAUNCHER; +import static org.eclipse.dataspacetck.core.system.ConsoleMonitor.ANSI_PROPERTY; +import static org.eclipse.dataspacetck.core.system.ConsoleMonitor.DEBUG_PROPERTY; /** * Launches the DSP TCK and runs the test suite. */ public class DspTckSuite { private static final String VERSION = "2024.1"; + private static final String CONFIG = "-config"; + private static final String DEFAULT_LAUNCHER = "org.eclipse.dataspacetck.dsp.system.DspSystemLauncher"; + private static final String TEST_PACKAGE = "org.eclipse.dataspacetck.dsp.verification"; public static void main(String... args) { - System.out.println("Running DSP TCK v" + VERSION); - + var properties = processEnv(args); + if (!properties.containsKey(TCK_LAUNCHER)) { + properties.put(TCK_LAUNCHER, DEFAULT_LAUNCHER); + } + var monitor = createMonitor(properties); + monitor.enableBold().message("\u001B[1mRunning DSP TCK v" + VERSION + "\u001B[0m").resetMode(); var result = TckRuntime.Builder.newInstance() - .launcherClass("org.eclipse.dataspacetck.dsp.system.DspSystemLauncher") - .addPackage("org.eclipse.dataspacetck.dsp.verification") + .properties(properties) + .addPackage(TEST_PACKAGE) + .monitor(monitor) .build().execute(); - new ConsoleResultWriter().output(result); + new ConsoleResultWriter(monitor).output(result); + + monitor.resetMode().message("Test run complete"); + } + + @NotNull + private static Monitor createMonitor(Map properties) { + var ansi = parseBoolean(properties.getOrDefault(ANSI_PROPERTY, "true")); + var debug = parseBoolean(properties.getOrDefault(DEBUG_PROPERTY, "false")); + return new ConsoleMonitor(debug, ansi); + } - System.out.println("Test run complete"); + private static Map processEnv(String[] args) { + if (args == null || args.length == 0) { + return null; + } + if (args.length != 2) { + throw new IllegalArgumentException("Invalid number of arguments: " + args.length); + } + if (!CONFIG.equals(args[0])) { + throw new IllegalArgumentException("Invalid argument: " + args[0]); + } + try (var reader = new FileReader(args[1])) { + var properties = new Properties(); + properties.load(reader); + //noinspection unchecked,rawtypes + return (Map) properties; + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/runtimes/dsp-tck/src/test/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuiteTest.java b/runtimes/dsp-tck/src/test/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuiteTest.java index 8b58792..f791cca 100644 --- a/runtimes/dsp-tck/src/test/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuiteTest.java +++ b/runtimes/dsp-tck/src/test/java/org/eclipse/dataspacetck/dsp/suite/DspTckSuiteTest.java @@ -14,6 +14,7 @@ package org.eclipse.dataspacetck.dsp.suite; +import org.eclipse.dataspacetck.core.system.ConsoleMonitor; import org.eclipse.dataspacetck.runtime.TckRuntime; import org.junit.jupiter.api.Test; @@ -24,9 +25,9 @@ class DspTckSuiteTest { @Test void verifyTestSuite() { var result = TckRuntime.Builder.newInstance() - .launcherClass("org.eclipse.dataspacetck.dsp.system.DspSystemLauncher") .property("dataspacetck.dsp.local.connector", "true") .addPackage("org.eclipse.dataspacetck.dsp.verification") + .monitor(new ConsoleMonitor(false, true)) .build().execute(); assertThat(result.getTestsSucceededCount()).isNotZero(); diff --git a/runtimes/tck-runtime/build.gradle.kts b/runtimes/tck-runtime/build.gradle.kts index 3b93673..c87a428 100644 --- a/runtimes/tck-runtime/build.gradle.kts +++ b/runtimes/tck-runtime/build.gradle.kts @@ -15,4 +15,7 @@ dependencies { implementation(libs.junit.platform.launcher) + implementation(libs.junit.platform.engine) + + implementation(project(":boot")) } diff --git a/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/ConsoleResultWriter.java b/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/ConsoleResultWriter.java index 129f7be..6f4b03d 100644 --- a/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/ConsoleResultWriter.java +++ b/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/ConsoleResultWriter.java @@ -14,42 +14,41 @@ package org.eclipse.dataspacetck.runtime; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; +import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.launcher.listeners.TestExecutionSummary; -import static java.lang.Boolean.parseBoolean; +import static java.lang.String.format; /** * Outputs to the console. */ public class ConsoleResultWriter { - private boolean ansi; + private Monitor monitor; - public ConsoleResultWriter() { - ansi = parseBoolean(System.getProperty("cvf.ansi", "true")); + public ConsoleResultWriter(Monitor monitor) { + this.monitor = monitor; } public void output(TestExecutionSummary result) { - System.out.println("\nSuccessful tests: " + result.getTestsSucceededCount()); - System.out.println("Failed tests: " + result.getTestsFailedCount()); + monitor.message("Passed tests: " + result.getTestsSucceededCount()); + monitor.message("Failed tests: " + result.getTestsFailedCount()); if (!result.getFailures().isEmpty()) { - System.out.printf("%n%sFailures:%n", ansiError()); - result.getFailures().forEach(f -> System.out.println(" -" + f.getException().getMessage())); - System.out.println(ansiReset()); + monitor.enableError().message("Failures:"); + result.getFailures() + .stream() + .filter(f -> f.getTestIdentifier().getSource().isPresent() && + f.getTestIdentifier().getSource().get() instanceof MethodSource) + .forEach(f -> { + var method = (MethodSource) f.getTestIdentifier().getSource().get(); + monitor.message(format("\n %c %s.%s\n", 'β– ', method.getClassName(), method.getMethodName())); + monitor.message(" [" + f.getTestIdentifier().getDisplayName() + "]"); + monitor.message(" " + f.getException().getMessage() + "\n"); + }); + monitor.resetMode(); } else { - System.out.printf("%sTest suite completed successfully%n", ansiSuccess()); - System.out.println(ansiReset()); + monitor.enableSuccess().message("πŸŽ‰πŸ˜ƒπŸš€All tests passed").resetMode(); } } - private String ansiError() { - return ansi ? "\u001B[31m" : ""; - } - - private String ansiSuccess() { - return ansi ? "\u001B[32m" : ""; - } - - private String ansiReset() { - return ansi ? "\u001B[0m" : ""; - } } diff --git a/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/TckExecutionListener.java b/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/TckExecutionListener.java new file mode 100644 index 0000000..ede570c --- /dev/null +++ b/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/TckExecutionListener.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.dataspacetck.runtime; + +import org.eclipse.dataspacetck.core.spi.boot.Monitor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; + +import static java.lang.String.format; + +/** + * Provides progress output during test execution. + */ +class TckExecutionListener implements TestExecutionListener { + private Monitor monitor; + + TckExecutionListener(Monitor monitor) { + this.monitor = monitor; + } + + @Override + public void executionStarted(TestIdentifier identifier) { + if (identifier.getSource().isPresent() && identifier.getSource().get() instanceof MethodSource) { + monitor.newLine().message("Started: " + identifier.getDisplayName()); + } + } + + @Override + public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { + if (identifier.getSource().isPresent() && identifier.getSource().get() instanceof MethodSource) { + var displayName = identifier.getDisplayName(); + if (displayName.contains(":")) { + displayName = displayName.substring(0, displayName.lastIndexOf(":")); + } + monitor.message(format("%s: %s%n", result.getStatus(), displayName)); + } + } + +} diff --git a/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/TckRuntime.java b/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/TckRuntime.java index a124684..76dc0ca 100644 --- a/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/TckRuntime.java +++ b/runtimes/tck-runtime/src/main/java/org/eclipse/dataspacetck/runtime/TckRuntime.java @@ -14,6 +14,7 @@ package org.eclipse.dataspacetck.runtime; +import org.eclipse.dataspacetck.core.spi.boot.Monitor; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; @@ -25,37 +26,36 @@ import java.util.List; import java.util.Map; -import static java.lang.System.setProperty; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; /** * Bootstraps the JUnit platform using the Jupiter engine and executes configured TCK tests. */ public class TckRuntime { - private static final String CVF_LAUNCHER = "cvf.launcher"; private static final String TEST_POSTFIX = ".*Test"; - private String launcherClass; + private Monitor monitor; + private List packages = new ArrayList<>(); private Map properties = new HashMap<>(); public TestExecutionSummary execute() { - var listener = new SummaryGeneratingListener(); - - setProperty(CVF_LAUNCHER, launcherClass); properties.forEach(System::setProperty); + var summaryListener = new SummaryGeneratingListener(); + var request = LauncherDiscoveryRequestBuilder.request() .filters(includeClassNamePatterns(TEST_POSTFIX)) .selectors(packages.stream().map(DiscoverySelectors::selectPackage).toList()) .build(); var launcher = LauncherFactory.create(); - launcher.registerTestExecutionListeners(listener); + launcher.registerTestExecutionListeners(new TckExecutionListener(monitor)); + launcher.registerTestExecutionListeners(summaryListener); launcher.discover(request); launcher.execute(request); - return listener.getSummary(); + return summaryListener.getSummary(); } private TckRuntime() { @@ -68,13 +68,13 @@ public static Builder newInstance() { return new Builder(); } - public Builder launcherClass(String clazz) { - launcher.launcherClass = clazz; + public Builder property(String key, String value) { + launcher.properties.put(key, value); return this; } - public Builder property(String key, String value) { - launcher.properties.put(key, value); + public Builder properties(Map properties) { + launcher.properties.putAll(properties); return this; } @@ -90,5 +90,12 @@ public TckRuntime build() { private Builder() { launcher = new TckRuntime(); } + + public Builder monitor(Monitor monitor) { + this.launcher.monitor = monitor; + return this; + } + } + } diff --git a/settings.gradle.kts b/settings.gradle.kts index 4603005..c9bcc05 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,6 +24,7 @@ */ rootProject.name = "cvf" +include("boot") include("core") include("dsp:dsp-contract-negotiation") include("dsp:dsp-system")