diff --git a/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateProcessor.java b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/Processor.java
similarity index 96%
rename from core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateProcessor.java
rename to core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/Processor.java
index 817d6d33fca..fdd0e895a4f 100644
--- a/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateProcessor.java
+++ b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/Processor.java
@@ -20,7 +20,7 @@
*
*/
@FunctionalInterface
-public interface StateProcessor {
+public interface Processor {
/**
* Process states
diff --git a/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/ProcessorImpl.java b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/ProcessorImpl.java
new file mode 100644
index 00000000000..d99f3f62e4e
--- /dev/null
+++ b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/ProcessorImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2022 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Contributors:
+ * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
+ *
+ */
+
+package org.eclipse.edc.statemachine;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import static java.util.function.Predicate.isEqual;
+
+/**
+ * Describes the processing flow applied by a state machine. The entities are provided by a supplier.
+ * A process is a function that returns a boolean that indicates if the entity has been processed or not in
+ * the scope of the function.
+ * The run method returns the processed state count, this is used by the state machine to decide
+ * to apply the wait strategy or not.
+ *
+ * An {@link Guard} can be registered, if its predicate is verified, the guard processor is executed instead of the standard one.
+ *
+ * @param the entity that is processed
+ */
+public class ProcessorImpl implements Processor {
+
+ private final Supplier> entities;
+ private Function process;
+ private Guard guard = Guard.noop();
+
+ private ProcessorImpl(Supplier> entitiesSupplier) {
+ entities = entitiesSupplier;
+ }
+
+ @Override
+ public Long process() {
+ return entities.get().stream()
+ .map(entity -> guard.predicate().test(entity)
+ ? guard.process().apply(entity) :
+ process.apply(entity))
+ .filter(isEqual(true))
+ .count();
+ }
+
+ public static class Builder {
+
+ private final ProcessorImpl processor;
+
+ public Builder(Supplier> entitiesSupplier) {
+ processor = new ProcessorImpl<>(entitiesSupplier);
+ }
+
+ public static Builder newInstance(Supplier> entitiesSupplier) {
+ return new Builder<>(entitiesSupplier);
+ }
+
+ public Builder process(Function process) {
+ processor.process = process;
+ return this;
+ }
+
+ public Builder guard(Predicate predicate, Function process) {
+ processor.guard = new Guard<>(predicate, process);
+ return this;
+ }
+
+ public ProcessorImpl build() {
+ Objects.requireNonNull(processor.process);
+
+ return processor;
+ }
+ }
+
+ private record Guard(Predicate predicate, Function process) {
+ static Guard noop() {
+ return new Guard<>(e -> false, e -> false);
+ }
+ }
+}
diff --git a/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateMachineManager.java b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateMachineManager.java
index 7af112b8f2b..476769468b1 100644
--- a/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateMachineManager.java
+++ b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateMachineManager.java
@@ -38,7 +38,7 @@
*/
public class StateMachineManager {
- private final List processors = new ArrayList<>();
+ private final List processors = new ArrayList<>();
private final ScheduledExecutorService executor;
private final AtomicBoolean active = new AtomicBoolean();
private final WaitStrategy waitStrategy;
@@ -65,7 +65,7 @@ private StateMachineManager(String name, Monitor monitor, ExecutorInstrumentatio
*/
public Future> start() {
active.set(true);
- return submit(0L);
+ return scheduleNextIterationIn(0L);
}
/**
@@ -95,41 +95,37 @@ public boolean isActive() {
return active.get();
}
- @NotNull
- private Future> submit(long delayMillis) {
- return executor.schedule(loop(), delayMillis, MILLISECONDS);
- }
-
private Runnable loop() {
return () -> {
if (active.get()) {
- long delay = performLogic();
-
- // Submit next execution after delay
- submit(delay);
+ performLogic();
}
};
}
- private long performLogic() {
+ private void performLogic() {
try {
var processed = processors.stream()
- .mapToLong(StateProcessor::process)
+ .mapToLong(Processor::process)
.sum();
waitStrategy.success();
- if (processed == 0) {
- return waitStrategy.waitForMillis();
- }
+ var delay = processed == 0 ? waitStrategy.waitForMillis() : 0;
+
+ scheduleNextIterationIn(delay);
} catch (Error e) {
active.set(false);
monitor.severe(format("StateMachineManager [%s] unrecoverable error", name), e);
} catch (Throwable e) {
monitor.severe(format("StateMachineManager [%s] error caught", name), e);
- return waitStrategy.retryInMillis();
+ scheduleNextIterationIn(waitStrategy.retryInMillis());
}
- return 0;
+ }
+
+ @NotNull
+ private Future> scheduleNextIterationIn(long delayMillis) {
+ return executor.schedule(loop(), delayMillis, MILLISECONDS);
}
public static class Builder {
@@ -144,7 +140,7 @@ public static Builder newInstance(String name, Monitor monitor, ExecutorInstrume
return new Builder(name, monitor, instrumentation, waitStrategy);
}
- public Builder processor(StateProcessor processor) {
+ public Builder processor(Processor processor) {
loop.processors.add(processor);
return this;
}
diff --git a/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateProcessorImpl.java b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateProcessorImpl.java
index dbfd062f33e..7d6c3f2e2d2 100644
--- a/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateProcessorImpl.java
+++ b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/StateProcessorImpl.java
@@ -29,8 +29,10 @@
* to apply the wait strategy or not.
*
* @param the entity that is processed
+ * @deprecated please use {@link ProcessorImpl}
*/
-public class StateProcessorImpl implements StateProcessor {
+@Deprecated(since = "0.1.3")
+public class StateProcessorImpl implements Processor {
private final Supplier> entities;
private final Function process;
diff --git a/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/ProcessorImplTest.java b/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/ProcessorImplTest.java
new file mode 100644
index 00000000000..c0cda0b25b1
--- /dev/null
+++ b/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/ProcessorImplTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2022 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 implementation
+ *
+ */
+
+package org.eclipse.edc.statemachine;
+
+import org.eclipse.edc.statemachine.retry.TestEntity;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.function.Function;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+class ProcessorImplTest {
+
+ @Test
+ void shouldReturnTheProcessedCount() {
+ var entity = TestEntity.Builder.newInstance().id("id").build();
+ var processor = ProcessorImpl.Builder.newInstance(() -> List.of(entity))
+ .process(e -> true)
+ .build();
+
+ var count = processor.process();
+
+ assertThat(count).isEqualTo(1);
+ }
+
+ @Test
+ void shouldNotCountUnprocessedEntities() {
+ var entity = TestEntity.Builder.newInstance().id("id").build();
+ var processor = ProcessorImpl.Builder.newInstance(() -> List.of(entity))
+ .process(e -> false)
+ .build();
+
+ var count = processor.process();
+
+ assertThat(count).isEqualTo(0);
+ }
+
+ @Test
+ void shouldExecuteGuard_whenItsPredicateMatches() {
+ var entity = TestEntity.Builder.newInstance().id("id").build();
+ Function process = mock();
+ Function guardProcess = mock();
+ when(guardProcess.apply(any())).thenReturn(true);
+ var processor = ProcessorImpl.Builder.newInstance(() -> List.of(entity))
+ .guard(e -> true, guardProcess)
+ .process(process)
+ .build();
+
+ var count = processor.process();
+
+ assertThat(count).isEqualTo(1);
+ verify(guardProcess).apply(entity);
+ verifyNoInteractions(process);
+ }
+
+ @Test
+ void shouldExecuteDefaultProcessor_whenGuardPredicateDoesNotMatch() {
+ var entity = TestEntity.Builder.newInstance().id("id").build();
+ Function process = mock();
+ Function guardProcess = mock();
+ when(process.apply(any())).thenReturn(true);
+ var processor = ProcessorImpl.Builder.newInstance(() -> List.of(entity))
+ .guard(e -> false, guardProcess)
+ .process(process)
+ .build();
+
+ var count = processor.process();
+
+ assertThat(count).isEqualTo(1);
+ verify(process).apply(entity);
+ verifyNoInteractions(guardProcess);
+ }
+}
diff --git a/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/StateMachineManagerTest.java b/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/StateMachineManagerTest.java
index 3de672201f2..f304095a457 100644
--- a/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/StateMachineManagerTest.java
+++ b/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/StateMachineManagerTest.java
@@ -46,7 +46,7 @@ void setUp() {
@Test
void shouldExecuteProcessorsAsyncAndCanBeStopped() throws InterruptedException {
- var processor = mock(StateProcessor.class);
+ var processor = mock(Processor.class);
when(processor.process()).thenAnswer(i -> {
Thread.sleep(100L);
return 1L;
@@ -68,7 +68,7 @@ void shouldExecuteProcessorsAsyncAndCanBeStopped() throws InterruptedException {
@Test
void shouldNotWaitForSomeTimeIfTheresAtLeastOneProcessedEntity() throws InterruptedException {
- var processor = mock(StateProcessor.class);
+ var processor = mock(Processor.class);
when(processor.process()).thenReturn(1L);
doAnswer(i -> {
return 1L;
@@ -87,7 +87,7 @@ void shouldNotWaitForSomeTimeIfTheresAtLeastOneProcessedEntity() throws Interrup
@Test
void shouldWaitForSomeTimeIfNoEntityIsProcessed() throws InterruptedException {
- var processor = mock(StateProcessor.class);
+ var processor = mock(Processor.class);
when(processor.process()).thenReturn(0L);
var waitStrategy = mock(WaitStrategy.class);
doAnswer(i -> {
@@ -107,7 +107,7 @@ void shouldWaitForSomeTimeIfNoEntityIsProcessed() throws InterruptedException {
@Test
void shouldExitWithAnExceptionIfProcessorExitsWithAnUnrecoverableError() {
- var processor = mock(StateProcessor.class);
+ var processor = mock(Processor.class);
when(processor.process()).thenThrow(new Error("unrecoverable"));
var stateMachine = StateMachineManager.Builder.newInstance("test", monitor, instrumentation, waitStrategy)
.processor(processor)
@@ -119,7 +119,7 @@ void shouldExitWithAnExceptionIfProcessorExitsWithAnUnrecoverableError() {
@Test
void shouldWaitRetryTimeWhenAnExceptionIsThrownByAnProcessor() throws InterruptedException {
- var processor = mock(StateProcessor.class);
+ var processor = mock(Processor.class);
when(processor.process()).thenThrow(new EdcException("exception")).thenReturn(0L);
when(waitStrategy.retryInMillis()).thenAnswer(i -> {
return 1L;
diff --git a/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/retry/TestEntity.java b/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/retry/TestEntity.java
index f9bac55b300..592203a0a35 100644
--- a/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/retry/TestEntity.java
+++ b/core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/retry/TestEntity.java
@@ -18,7 +18,7 @@
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import org.eclipse.edc.spi.entity.StatefulEntity;
-class TestEntity extends StatefulEntity {
+public class TestEntity extends StatefulEntity {
@Override
public TestEntity copy() {
return this;
@@ -47,7 +47,7 @@ public Builder self() {
}
@Override
- protected TestEntity build() {
+ public TestEntity build() {
return super.build();
}
}
diff --git a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractCoreExtension.java b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractCoreExtension.java
index 8963944e467..0a6d843e029 100644
--- a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractCoreExtension.java
+++ b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractCoreExtension.java
@@ -20,22 +20,18 @@
import org.eclipse.edc.connector.contract.listener.ContractNegotiationEventListener;
import org.eclipse.edc.connector.contract.negotiation.ConsumerContractNegotiationManagerImpl;
import org.eclipse.edc.connector.contract.negotiation.ProviderContractNegotiationManagerImpl;
-import org.eclipse.edc.connector.contract.observe.ContractNegotiationObservableImpl;
-import org.eclipse.edc.connector.contract.offer.ContractDefinitionResolverImpl;
-import org.eclipse.edc.connector.contract.policy.PolicyArchiveImpl;
import org.eclipse.edc.connector.contract.policy.PolicyEquality;
import org.eclipse.edc.connector.contract.spi.negotiation.ConsumerContractNegotiationManager;
+import org.eclipse.edc.connector.contract.spi.negotiation.ContractNegotiationPendingGuard;
import org.eclipse.edc.connector.contract.spi.negotiation.NegotiationWaitStrategy;
import org.eclipse.edc.connector.contract.spi.negotiation.ProviderContractNegotiationManager;
import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationObservable;
import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver;
-import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
import org.eclipse.edc.connector.contract.spi.validation.ContractValidationService;
import org.eclipse.edc.connector.contract.validation.ContractExpiryCheckFunction;
import org.eclipse.edc.connector.contract.validation.ContractValidationServiceImpl;
-import org.eclipse.edc.connector.policy.spi.store.PolicyArchive;
import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore;
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
@@ -67,8 +63,8 @@
import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA;
@Provides({
- ContractValidationService.class, ConsumerContractNegotiationManager.class, PolicyArchive.class,
- ProviderContractNegotiationManager.class, ContractNegotiationObservable.class, ContractDefinitionResolver.class
+ ContractValidationService.class, ConsumerContractNegotiationManager.class,
+ ProviderContractNegotiationManager.class
})
@CoreExtension
@Extension(value = ContractCoreExtension.NAME)
@@ -109,9 +105,6 @@ public class ContractCoreExtension implements ServiceExtension {
@Inject
private AssetIndex assetIndex;
- @Inject
- private ContractDefinitionStore contractDefinitionStore;
-
@Inject
private RemoteMessageDispatcherRegistry dispatcherRegistry;
@@ -148,6 +141,18 @@ public class ContractCoreExtension implements ServiceExtension {
@Inject
private ProtocolWebhook protocolWebhook;
+ @Inject
+ private ContractDefinitionResolver contractDefinitionResolver;
+
+ @Inject
+ private ContractNegotiationObservable observable;
+
+ @Inject
+ private ContractNegotiationPendingGuard pendingGuard;
+
+ @Inject
+ private ExecutorInstrumentation executorInstrumentation;
+
@Override
public String name() {
return NAME;
@@ -177,13 +182,10 @@ public void shutdown() {
}
private void registerServices(ServiceExtensionContext context) {
- var definitionService = new ContractDefinitionResolverImpl(monitor, contractDefinitionStore, policyEngine, policyStore);
- context.registerService(ContractDefinitionResolver.class, definitionService);
-
var participantId = context.getParticipantId();
var policyEquality = new PolicyEquality(typeManager);
- var validationService = new ContractValidationServiceImpl(participantId, agentService, definitionService, assetIndex, policyStore, policyEngine, policyEquality);
+ var validationService = new ContractValidationServiceImpl(participantId, agentService, contractDefinitionResolver, assetIndex, policyStore, policyEngine, policyEquality);
context.registerService(ContractValidationService.class, validationService);
// bind/register rule to evaluate contract expiry
@@ -193,16 +195,11 @@ private void registerServices(ServiceExtensionContext context) {
var function = new ContractExpiryCheckFunction();
policyEngine.registerFunction(TRANSFER_SCOPE, Permission.class, CONTRACT_EXPIRY_EVALUATION_KEY, function);
-
var iterationWaitMillis = context.getSetting(NEGOTIATION_STATE_MACHINE_ITERATION_WAIT_MILLIS, DEFAULT_ITERATION_WAIT);
var waitStrategy = context.hasService(NegotiationWaitStrategy.class) ? context.getService(NegotiationWaitStrategy.class) : new ExponentialWaitStrategy(iterationWaitMillis);
- var observable = new ContractNegotiationObservableImpl();
observable.registerListener(new ContractNegotiationEventListener(eventRouter, clock));
- context.registerService(ContractNegotiationObservable.class, observable);
- context.registerService(PolicyArchive.class, new PolicyArchiveImpl(store));
-
consumerNegotiationManager = ConsumerContractNegotiationManagerImpl.Builder.newInstance()
.participantId(participantId)
.waitStrategy(waitStrategy)
@@ -211,12 +208,13 @@ private void registerServices(ServiceExtensionContext context) {
.observable(observable)
.clock(clock)
.telemetry(telemetry)
- .executorInstrumentation(context.getService(ExecutorInstrumentation.class))
+ .executorInstrumentation(executorInstrumentation)
.store(store)
.policyStore(policyStore)
.batchSize(context.getSetting(NEGOTIATION_CONSUMER_STATE_MACHINE_BATCH_SIZE, DEFAULT_BATCH_SIZE))
.entityRetryProcessConfiguration(consumerEntityRetryProcessConfiguration(context))
.protocolWebhook(protocolWebhook)
+ .pendingGuard(pendingGuard)
.build();
providerNegotiationManager = ProviderContractNegotiationManagerImpl.Builder.newInstance()
@@ -227,12 +225,13 @@ private void registerServices(ServiceExtensionContext context) {
.observable(observable)
.clock(clock)
.telemetry(telemetry)
- .executorInstrumentation(context.getService(ExecutorInstrumentation.class))
+ .executorInstrumentation(executorInstrumentation)
.store(store)
.policyStore(policyStore)
.batchSize(context.getSetting(NEGOTIATION_PROVIDER_STATE_MACHINE_BATCH_SIZE, DEFAULT_BATCH_SIZE))
.entityRetryProcessConfiguration(providerEntityRetryProcessConfiguration(context))
.protocolWebhook(protocolWebhook)
+ .pendingGuard(pendingGuard)
.build();
context.registerService(ConsumerContractNegotiationManager.class, consumerNegotiationManager);
diff --git a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractNegotiationDefaultServicesExtension.java b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractNegotiationDefaultServicesExtension.java
new file mode 100644
index 00000000000..7f32d58231b
--- /dev/null
+++ b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractNegotiationDefaultServicesExtension.java
@@ -0,0 +1,73 @@
+/*
+ * 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.edc.connector.contract;
+
+import org.eclipse.edc.connector.contract.observe.ContractNegotiationObservableImpl;
+import org.eclipse.edc.connector.contract.offer.ContractDefinitionResolverImpl;
+import org.eclipse.edc.connector.contract.policy.PolicyArchiveImpl;
+import org.eclipse.edc.connector.contract.spi.negotiation.ContractNegotiationPendingGuard;
+import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationObservable;
+import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
+import org.eclipse.edc.connector.contract.spi.offer.ContractDefinitionResolver;
+import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore;
+import org.eclipse.edc.connector.policy.spi.store.PolicyArchive;
+import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore;
+import org.eclipse.edc.policy.engine.spi.PolicyEngine;
+import org.eclipse.edc.runtime.metamodel.annotation.Extension;
+import org.eclipse.edc.runtime.metamodel.annotation.Inject;
+import org.eclipse.edc.runtime.metamodel.annotation.Provider;
+import org.eclipse.edc.spi.system.ServiceExtension;
+import org.eclipse.edc.spi.system.ServiceExtensionContext;
+
+/**
+ * Contract Negotiation Default Services Extension
+ */
+@Extension(value = ContractNegotiationDefaultServicesExtension.NAME)
+public class ContractNegotiationDefaultServicesExtension implements ServiceExtension {
+
+ public static final String NAME = "Contract Negotiation Default Services";
+
+ @Inject
+ private ContractDefinitionStore contractDefinitionStore;
+
+ @Inject
+ private PolicyEngine policyEngine;
+
+ @Inject
+ private PolicyDefinitionStore policyStore;
+
+ @Inject
+ private ContractNegotiationStore store;
+
+ @Provider
+ public ContractDefinitionResolver contractDefinitionResolver(ServiceExtensionContext context) {
+ return new ContractDefinitionResolverImpl(context.getMonitor(), contractDefinitionStore, policyEngine, policyStore);
+ }
+
+ @Provider
+ public ContractNegotiationObservable observable() {
+ return new ContractNegotiationObservableImpl();
+ }
+
+ @Provider
+ public PolicyArchive policyArchive() {
+ return new PolicyArchiveImpl(store);
+ }
+
+ @Provider(isDefault = true)
+ public ContractNegotiationPendingGuard pendingGuard() {
+ return it -> false;
+ }
+}
diff --git a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListener.java b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListener.java
index b7e7c2d21f8..2ae1e10f3e3 100644
--- a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListener.java
+++ b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListener.java
@@ -16,9 +16,7 @@
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAccepted;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAgreed;
-import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationDeclined;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationEvent;
-import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFailed;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationInitiated;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationOffered;
@@ -71,18 +69,6 @@ public void terminated(ContractNegotiation negotiation) {
publish(event);
}
- @Override
- public void declined(ContractNegotiation negotiation) {
- var event = baseBuilder(ContractNegotiationDeclined.Builder.newInstance(), negotiation).build();
- publish(event);
- }
-
- @Override
- public void failed(ContractNegotiation negotiation) {
- var event = baseBuilder(ContractNegotiationFailed.Builder.newInstance(), negotiation).build();
- publish(event);
- }
-
@Override
public void agreed(ContractNegotiation negotiation) {
var event = baseBuilder(ContractNegotiationAgreed.Builder.newInstance(), negotiation).build();
diff --git a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/AbstractContractNegotiationManager.java b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/AbstractContractNegotiationManager.java
index 1dcf8950d79..5f3ecf3814d 100644
--- a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/AbstractContractNegotiationManager.java
+++ b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/AbstractContractNegotiationManager.java
@@ -14,6 +14,7 @@
package org.eclipse.edc.connector.contract.negotiation;
+import org.eclipse.edc.connector.contract.spi.negotiation.ContractNegotiationPendingGuard;
import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationObservable;
import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement;
@@ -23,21 +24,27 @@
import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.protocol.ProtocolWebhook;
+import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.retry.ExponentialWaitStrategy;
import org.eclipse.edc.spi.retry.WaitStrategy;
import org.eclipse.edc.spi.system.ExecutorInstrumentation;
import org.eclipse.edc.spi.telemetry.Telemetry;
+import org.eclipse.edc.statemachine.Processor;
+import org.eclipse.edc.statemachine.ProcessorImpl;
import org.eclipse.edc.statemachine.retry.EntityRetryProcessConfiguration;
import org.eclipse.edc.statemachine.retry.EntityRetryProcessFactory;
import org.jetbrains.annotations.NotNull;
import java.time.Clock;
import java.util.Objects;
+import java.util.function.Function;
import static org.eclipse.edc.connector.contract.ContractCoreExtension.DEFAULT_BATCH_SIZE;
import static org.eclipse.edc.connector.contract.ContractCoreExtension.DEFAULT_ITERATION_WAIT;
import static org.eclipse.edc.connector.contract.ContractCoreExtension.DEFAULT_SEND_RETRY_BASE_DELAY;
import static org.eclipse.edc.connector.contract.ContractCoreExtension.DEFAULT_SEND_RETRY_LIMIT;
+import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState;
+import static org.eclipse.edc.spi.persistence.StateEntityStore.isNotPending;
public abstract class AbstractContractNegotiationManager {
protected String participantId;
@@ -54,6 +61,23 @@ public abstract class AbstractContractNegotiationManager {
protected EntityRetryProcessFactory entityRetryProcessFactory;
protected EntityRetryProcessConfiguration entityRetryProcessConfiguration = defaultEntityRetryProcessConfiguration();
protected ProtocolWebhook protocolWebhook;
+ protected ContractNegotiationPendingGuard pendingGuard = it -> false;
+
+ abstract ContractNegotiation.Type type();
+
+ protected Processor processNegotiationsInState(ContractNegotiationStates state, Function function) {
+ var filter = new Criterion[] { hasState(state.code()), isNotPending(), new Criterion("type", "=", type().name()) };
+ return ProcessorImpl.Builder.newInstance(() -> negotiationStore.nextNotLeased(batchSize, filter))
+ .process(telemetry.contextPropagationMiddleware(function))
+ .guard(pendingGuard, this::setPending)
+ .build();
+ }
+
+ private boolean setPending(ContractNegotiation contractNegotiation) {
+ contractNegotiation.setPending(true);
+ update(contractNegotiation);
+ return true;
+ }
protected void transitionToInitial(ContractNegotiation negotiation) {
negotiation.transitionInitial();
@@ -231,6 +255,11 @@ public Builder protocolWebhook(ProtocolWebhook protocolWebhook) {
return this;
}
+ public Builder pendingGuard(ContractNegotiationPendingGuard pendingGuard) {
+ manager.pendingGuard = pendingGuard;
+ return this;
+ }
+
public T build() {
Objects.requireNonNull(manager.participantId, "participantId");
Objects.requireNonNull(manager.monitor, "monitor");
diff --git a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImpl.java b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImpl.java
index 05e4dd522fc..90838da4512 100644
--- a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImpl.java
+++ b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImpl.java
@@ -25,17 +25,13 @@
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementMessage;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementVerificationMessage;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
-import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationTerminationMessage;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequestMessage;
-import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.response.StatusResult;
import org.eclipse.edc.statemachine.StateMachineManager;
-import org.eclipse.edc.statemachine.StateProcessorImpl;
import java.util.UUID;
-import java.util.function.Function;
import static java.lang.String.format;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation.Type.CONSUMER;
@@ -45,7 +41,6 @@
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTING;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATING;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.VERIFYING;
-import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState;
/**
* Implementation of the {@link ConsumerContractNegotiationManager}.
@@ -104,6 +99,11 @@ public StatusResult initiate(ContractRequest request) {
return StatusResult.success(negotiation);
}
+ @Override
+ ContractNegotiation.Type type() {
+ return CONSUMER;
+ }
+
/**
* Processes {@link ContractNegotiation} in state INITIAL. Transition ContractNegotiation to REQUESTING.
*
@@ -254,11 +254,6 @@ private boolean processTerminating(ContractNegotiation negotiation) {
.execute("[Consumer] send rejection");
}
- private StateProcessorImpl processNegotiationsInState(ContractNegotiationStates state, Function function) {
- var filter = new Criterion[]{ hasState(state.code()), new Criterion("type", "=", CONSUMER.name()) };
- return new StateProcessorImpl<>(() -> negotiationStore.nextNotLeased(batchSize, filter), telemetry.contextPropagationMiddleware(function));
- }
-
/**
* Builder for ConsumerContractNegotiationManagerImpl.
*/
diff --git a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImpl.java b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImpl.java
index 08a3a36f8a9..2c3be0ec06d 100644
--- a/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImpl.java
+++ b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImpl.java
@@ -25,14 +25,9 @@
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementMessage;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractNegotiationEventMessage;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
-import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationTerminationMessage;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractOfferMessage;
-import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.statemachine.StateMachineManager;
-import org.eclipse.edc.statemachine.StateProcessorImpl;
-
-import java.util.function.Function;
import static java.lang.String.format;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation.Type.PROVIDER;
@@ -42,7 +37,6 @@
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTED;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATING;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.VERIFIED;
-import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState;
/**
* Implementation of the {@link ProviderContractNegotiationManager}.
@@ -72,9 +66,9 @@ public void stop() {
}
}
- private StateProcessorImpl processNegotiationsInState(ContractNegotiationStates state, Function function) {
- var filter = new Criterion[]{hasState(state.code()), new Criterion("type", "=", PROVIDER.name())};
- return new StateProcessorImpl<>(() -> negotiationStore.nextNotLeased(batchSize, filter), telemetry.contextPropagationMiddleware(function));
+ @Override
+ protected ContractNegotiation.Type type() {
+ return PROVIDER;
}
/**
diff --git a/core/control-plane/contract-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/core/control-plane/contract-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
index 6192e04bdfc..8f9b132b59e 100644
--- a/core/control-plane/contract-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
+++ b/core/control-plane/contract-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
@@ -15,3 +15,4 @@
org.eclipse.edc.connector.contract.ContractCoreExtension
org.eclipse.edc.connector.contract.ContractNegotiationCommandExtension
+org.eclipse.edc.connector.contract.ContractNegotiationDefaultServicesExtension
diff --git a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/ContractCoreExtensionTest.java b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/ContractCoreExtensionTest.java
index a2921ea2c50..4c498a40681 100644
--- a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/ContractCoreExtensionTest.java
+++ b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/ContractCoreExtensionTest.java
@@ -14,35 +14,23 @@
package org.eclipse.edc.connector.contract;
-import org.eclipse.edc.connector.contract.policy.PolicyArchiveImpl;
-import org.eclipse.edc.connector.policy.spi.store.PolicyArchive;
+import org.eclipse.edc.connector.contract.spi.negotiation.ConsumerContractNegotiationManager;
+import org.eclipse.edc.connector.contract.spi.negotiation.ProviderContractNegotiationManager;
import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
-import org.eclipse.edc.spi.system.ExecutorInstrumentation;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
-import org.eclipse.edc.spi.system.injection.ObjectFactory;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
@ExtendWith(DependencyInjectionExtension.class)
class ContractCoreExtensionTest {
- private ContractCoreExtension extension;
-
- @BeforeEach
- void setUp(ObjectFactory factory, ServiceExtensionContext context) {
- context.registerService(ExecutorInstrumentation.class, mock(ExecutorInstrumentation.class));
- extension = factory.constructInstance(ContractCoreExtension.class);
- }
-
@Test
- void shouldProvidePolicyArchive(ServiceExtensionContext context) {
+ void shouldProvideManagers(ContractCoreExtension extension, ServiceExtensionContext context) {
extension.initialize(context);
- assertThat(context.hasService(PolicyArchive.class)).isTrue();
- assertThat(context.getService(PolicyArchive.class)).isNotNull().isInstanceOf(PolicyArchiveImpl.class);
+ assertThat(context.hasService(ConsumerContractNegotiationManager.class)).isTrue();
+ assertThat(context.hasService(ProviderContractNegotiationManager.class)).isTrue();
}
}
diff --git a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListenerTest.java b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListenerTest.java
index be8e0ff0d1e..e75542de45f 100644
--- a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListenerTest.java
+++ b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/listener/ContractNegotiationEventListenerTest.java
@@ -16,9 +16,7 @@
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAccepted;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAgreed;
-import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationDeclined;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationEvent;
-import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFailed;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationInitiated;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationOffered;
@@ -72,7 +70,6 @@ void initiated_shouldDispatchEvent() {
.build();
assertEvent(eventPayload);
-
}
@Test
@@ -151,45 +148,6 @@ void terminated_shouldDispatchEvent() {
}
- @Test
- void declined_shouldDispatchEvent() {
- var listener = new ContractNegotiationEventListener(router, clock);
- var negotiation = getNegotiation("id");
-
- listener.declined(negotiation);
-
- var eventPayload = ContractNegotiationDeclined.Builder.newInstance()
- .contractNegotiationId(negotiation.getId())
- .counterPartyAddress(negotiation.getCounterPartyAddress())
- .protocol(negotiation.getProtocol())
- .callbackAddresses(negotiation.getCallbackAddresses())
- .counterPartyId(negotiation.getCounterPartyId())
- .build();
-
- assertEvent(eventPayload);
-
- }
-
- @Test
- void failed_shouldDispatchEvent() {
- var listener = new ContractNegotiationEventListener(router, clock);
- var negotiation = getNegotiation("id");
-
- listener.failed(negotiation);
-
- var eventPayload = ContractNegotiationFailed.Builder.newInstance()
- .contractNegotiationId(negotiation.getId())
- .counterPartyAddress(negotiation.getCounterPartyAddress())
- .protocol(negotiation.getProtocol())
- .callbackAddresses(negotiation.getCallbackAddresses())
- .counterPartyId(negotiation.getCounterPartyId())
- .build();
-
- assertEvent(eventPayload);
-
- }
-
-
@Test
void agreed_shouldDispatchEvent() {
var listener = new ContractNegotiationEventListener(router, clock);
@@ -257,7 +215,6 @@ void finalized_shouldDispatchEvent() {
}
-
private ContractNegotiation getNegotiation(String id) {
return getNegotiationBuilder(id).build();
}
diff --git a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImplTest.java b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImplTest.java
index b655eb5204e..6e5e316b469 100644
--- a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImplTest.java
+++ b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ConsumerContractNegotiationManagerImplTest.java
@@ -16,6 +16,7 @@
package org.eclipse.edc.connector.contract.negotiation;
import org.eclipse.edc.connector.contract.observe.ContractNegotiationObservableImpl;
+import org.eclipse.edc.connector.contract.spi.negotiation.ContractNegotiationPendingGuard;
import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationListener;
import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement;
@@ -68,6 +69,7 @@
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.VERIFIED;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.VERIFYING;
import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState;
+import static org.eclipse.edc.spi.persistence.StateEntityStore.isNotPending;
import static org.eclipse.edc.spi.response.ResponseStatus.FATAL_ERROR;
import static org.mockito.AdditionalMatchers.and;
import static org.mockito.AdditionalMatchers.aryEq;
@@ -92,7 +94,8 @@ class ConsumerContractNegotiationManagerImplTest {
private final ContractNegotiationListener listener = mock();
private final ProtocolWebhook protocolWebhook = mock();
private final String protocolWebhookUrl = "http://protocol.webhook/url";
- private ConsumerContractNegotiationManagerImpl negotiationManager;
+ private final ContractNegotiationPendingGuard pendingGuard = mock();
+ private ConsumerContractNegotiationManagerImpl manager;
@BeforeEach
void setUp() {
@@ -101,7 +104,7 @@ void setUp() {
var observable = new ContractNegotiationObservableImpl();
observable.registerListener(listener);
- negotiationManager = ConsumerContractNegotiationManagerImpl.Builder.newInstance()
+ manager = ConsumerContractNegotiationManagerImpl.Builder.newInstance()
.participantId(PARTICIPANT_ID)
.dispatcherRegistry(dispatcherRegistry)
.monitor(mock(Monitor.class))
@@ -110,6 +113,7 @@ void setUp() {
.policyStore(policyStore)
.entityRetryProcessConfiguration(new EntityRetryProcessConfiguration(RETRY_LIMIT, () -> new ExponentialWaitStrategy(0L)))
.protocolWebhook(protocolWebhook)
+ .pendingGuard(pendingGuard)
.build();
}
@@ -127,7 +131,7 @@ void initiate_shouldSaveNewNegotiationInInitialState() {
.build()))
.build();
- var result = negotiationManager.initiate(request);
+ var result = manager.initiate(request);
assertThat(result.succeeded()).isTrue();
verify(store).save(argThat(negotiation ->
@@ -148,7 +152,7 @@ void initial_shouldTransitionRequesting() {
var negotiation = contractNegotiationBuilder().state(INITIAL.code()).build();
when(store.nextNotLeased(anyInt(), stateIs(INITIAL.code()))).thenReturn(List.of(negotiation)).thenReturn(emptyList());
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == REQUESTING.code()));
@@ -163,7 +167,7 @@ void requesting_shouldSendOfferAndTransitionRequested() {
when(store.findById(negotiation.getId())).thenReturn(negotiation);
when(protocolWebhook.url()).thenReturn(protocolWebhookUrl);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == REQUESTED.code()));
@@ -179,7 +183,7 @@ void accepting_shouldSendAgreementAndTransitionToApproved() {
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(completedFuture(StatusResult.success("any")));
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == ACCEPTED.code()));
@@ -194,7 +198,7 @@ void agreed_shouldTransitionToVerifying() {
when(store.nextNotLeased(anyInt(), stateIs(AGREED.code()))).thenReturn(List.of(negotiation)).thenReturn(emptyList());
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == VERIFYING.code()));
@@ -209,7 +213,7 @@ void verifying_shouldSendMessageAndTransitionToVerified() {
when(store.findById(negotiation.getId())).thenReturn(negotiation);
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(completedFuture(StatusResult.success("any")));
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == VERIFIED.code()));
@@ -225,7 +229,7 @@ void terminating_shouldSendRejectionAndTransitionTerminated() {
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(completedFuture(StatusResult.success("any")));
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == TERMINATED.code()));
@@ -234,6 +238,24 @@ void terminating_shouldSendRejectionAndTransitionTerminated() {
});
}
+ @Test
+ void pendingGuard_shouldSetTheTransferPending_whenPendingGuardMatches() {
+ when(pendingGuard.test(any())).thenReturn(true);
+ var process = contractNegotiationBuilder().state(VERIFYING.code()).build();
+ when(store.nextNotLeased(anyInt(), stateIs(VERIFYING.code()))).thenReturn(List.of(process)).thenReturn(emptyList());
+
+ manager.start();
+
+ await().untilAsserted(() -> {
+ verify(pendingGuard).test(any());
+ var captor = ArgumentCaptor.forClass(ContractNegotiation.class);
+ verify(store).save(captor.capture());
+ var saved = captor.getValue();
+ assertThat(saved.getState()).isEqualTo(VERIFYING.code());
+ assertThat(saved.isPending()).isTrue();
+ });
+ }
+
@ParameterizedTest
@ArgumentsSource(DispatchFailureArguments.class)
void dispatchException(ContractNegotiationStates starting, ContractNegotiationStates ending, CompletableFuture> result, UnaryOperator builderEnricher) {
@@ -242,7 +264,7 @@ void dispatchException(ContractNegotiationStates starting, ContractNegotiationSt
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(result);
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
var captor = ArgumentCaptor.forClass(ContractNegotiation.class);
@@ -255,7 +277,7 @@ void dispatchException(ContractNegotiationStates starting, ContractNegotiationSt
}
private Criterion[] stateIs(int state) {
- return aryEq(new Criterion[]{ hasState(state), new Criterion("type", "=", "CONSUMER") });
+ return aryEq(new Criterion[]{ hasState(state), isNotPending(), new Criterion("type", "=", "CONSUMER") });
}
private ContractNegotiation.Builder contractNegotiationBuilder() {
diff --git a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImplTest.java b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImplTest.java
index 7534e9ec02b..c5999525d05 100644
--- a/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImplTest.java
+++ b/core/control-plane/contract-core/src/test/java/org/eclipse/edc/connector/contract/negotiation/ProviderContractNegotiationManagerImplTest.java
@@ -17,6 +17,7 @@
import org.eclipse.edc.connector.contract.observe.ContractNegotiationObservableImpl;
import org.eclipse.edc.connector.contract.spi.ContractId;
+import org.eclipse.edc.connector.contract.spi.negotiation.ContractNegotiationPendingGuard;
import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationListener;
import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore;
import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement;
@@ -68,6 +69,7 @@
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATING;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.VERIFIED;
import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState;
+import static org.eclipse.edc.spi.persistence.StateEntityStore.isNotPending;
import static org.eclipse.edc.spi.response.ResponseStatus.FATAL_ERROR;
import static org.mockito.AdditionalMatchers.and;
import static org.mockito.AdditionalMatchers.aryEq;
@@ -89,13 +91,14 @@ class ProviderContractNegotiationManagerImplTest {
private final RemoteMessageDispatcherRegistry dispatcherRegistry = mock(RemoteMessageDispatcherRegistry.class);
private final PolicyDefinitionStore policyStore = mock(PolicyDefinitionStore.class);
private final ContractNegotiationListener listener = mock(ContractNegotiationListener.class);
- private ProviderContractNegotiationManagerImpl negotiationManager;
+ private final ContractNegotiationPendingGuard pendingGuard = mock();
+ private ProviderContractNegotiationManagerImpl manager;
@BeforeEach
void setUp() {
var observable = new ContractNegotiationObservableImpl();
observable.registerListener(listener);
- negotiationManager = ProviderContractNegotiationManagerImpl.Builder.newInstance()
+ manager = ProviderContractNegotiationManagerImpl.Builder.newInstance()
.participantId(PROVIDER_ID)
.dispatcherRegistry(dispatcherRegistry)
.monitor(mock(Monitor.class))
@@ -103,6 +106,7 @@ void setUp() {
.store(store)
.policyStore(policyStore)
.entityRetryProcessConfiguration(new EntityRetryProcessConfiguration(RETRY_LIMIT, () -> new ExponentialWaitStrategy(0L)))
+ .pendingGuard(pendingGuard)
.build();
}
@@ -113,7 +117,7 @@ void offering_shouldSendOfferAndTransitionToOffered() {
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(completedFuture(StatusResult.success("any")));
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == OFFERED.code()));
@@ -128,7 +132,7 @@ void requested_shouldTransitionToAgreeing() {
when(store.nextNotLeased(anyInt(), stateIs(REQUESTED.code()))).thenReturn(List.of(negotiation)).thenReturn(emptyList());
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == AGREEING.code()));
@@ -142,7 +146,7 @@ void verified_shouldTransitionToFinalizing() {
when(store.nextNotLeased(anyInt(), stateIs(VERIFIED.code()))).thenReturn(List.of(negotiation)).thenReturn(emptyList());
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == FINALIZING.code()));
@@ -162,7 +166,7 @@ void agreeing_shouldSendAgreementAndTransitionToConfirmed() {
when(store.findById(negotiation.getId())).thenReturn(negotiation);
when(policyStore.findById(any())).thenReturn(PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).id("policyId").build());
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == AGREED.code()));
@@ -178,7 +182,7 @@ void finalizing_shouldSendMessageAndTransitionToFinalized() {
when(store.findById(negotiation.getId())).thenReturn(negotiation);
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(completedFuture(StatusResult.success("any")));
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == FINALIZED.code()));
@@ -194,7 +198,7 @@ void terminating_shouldSendMessageAndTransitionTerminated() {
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(completedFuture(StatusResult.success("any")));
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
verify(store).save(argThat(p -> p.getState() == TERMINATED.code()));
@@ -203,6 +207,24 @@ void terminating_shouldSendMessageAndTransitionTerminated() {
});
}
+ @Test
+ void pendingGuard_shouldSetTheTransferPending_whenPendingGuardMatches() {
+ when(pendingGuard.test(any())).thenReturn(true);
+ var process = contractNegotiationBuilder().state(AGREEING.code()).build();
+ when(store.nextNotLeased(anyInt(), stateIs(AGREEING.code()))).thenReturn(List.of(process)).thenReturn(emptyList());
+
+ manager.start();
+
+ await().untilAsserted(() -> {
+ verify(pendingGuard).test(any());
+ var captor = ArgumentCaptor.forClass(ContractNegotiation.class);
+ verify(store).save(captor.capture());
+ var saved = captor.getValue();
+ assertThat(saved.getState()).isEqualTo(AGREEING.code());
+ assertThat(saved.isPending()).isTrue();
+ });
+ }
+
@ParameterizedTest
@ArgumentsSource(DispatchFailureArguments.class)
void dispatchException(ContractNegotiationStates starting, ContractNegotiationStates ending, CompletableFuture> result, UnaryOperator builderEnricher) {
@@ -211,7 +233,7 @@ void dispatchException(ContractNegotiationStates starting, ContractNegotiationSt
when(dispatcherRegistry.dispatch(any(), any())).thenReturn(result);
when(store.findById(negotiation.getId())).thenReturn(negotiation);
- negotiationManager.start();
+ manager.start();
await().untilAsserted(() -> {
var captor = ArgumentCaptor.forClass(ContractNegotiation.class);
@@ -253,7 +275,7 @@ private ContractOffer contractOffer() {
}
private Criterion[] stateIs(int state) {
- return aryEq(new Criterion[]{ hasState(state), new Criterion("type", "=", "PROVIDER") });
+ return aryEq(new Criterion[]{ hasState(state), isNotPending(), new Criterion("type", "=", "PROVIDER") });
}
private static class DispatchFailureArguments implements ArgumentsProvider {
diff --git a/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferCoreExtension.java b/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferCoreExtension.java
index 51d309fa915..4de2ebee189 100644
--- a/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferCoreExtension.java
+++ b/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferCoreExtension.java
@@ -20,16 +20,12 @@
import org.eclipse.edc.connector.transfer.command.handlers.DeprovisionCompleteCommandHandler;
import org.eclipse.edc.connector.transfer.edr.DataAddressToEndpointDataReferenceTransformer;
import org.eclipse.edc.connector.transfer.edr.EndpointDataReferenceReceiverRegistryImpl;
-import org.eclipse.edc.connector.transfer.flow.DataFlowManagerImpl;
import org.eclipse.edc.connector.transfer.listener.TransferProcessEventListener;
-import org.eclipse.edc.connector.transfer.observe.TransferProcessObservableImpl;
-import org.eclipse.edc.connector.transfer.process.StatusCheckerRegistryImpl;
import org.eclipse.edc.connector.transfer.process.TransferProcessManagerImpl;
import org.eclipse.edc.connector.transfer.provision.DeprovisionResponsesHandler;
-import org.eclipse.edc.connector.transfer.provision.ProvisionManagerImpl;
import org.eclipse.edc.connector.transfer.provision.ProvisionResponsesHandler;
-import org.eclipse.edc.connector.transfer.provision.ResourceManifestGeneratorImpl;
import org.eclipse.edc.connector.transfer.spi.TransferProcessManager;
+import org.eclipse.edc.connector.transfer.spi.TransferProcessPendingGuard;
import org.eclipse.edc.connector.transfer.spi.edr.EndpointDataReferenceReceiverRegistry;
import org.eclipse.edc.connector.transfer.spi.event.TransferProcessStarted;
import org.eclipse.edc.connector.transfer.spi.flow.DataFlowManager;
@@ -42,7 +38,6 @@
import org.eclipse.edc.connector.transfer.spi.types.DataRequest;
import org.eclipse.edc.connector.transfer.spi.types.DeprovisionedResource;
import org.eclipse.edc.connector.transfer.spi.types.ProvisionedContentResource;
-import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.runtime.metamodel.annotation.CoreExtension;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
@@ -62,6 +57,7 @@
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.statemachine.retry.EntityRetryProcessConfiguration;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
+import org.jetbrains.annotations.NotNull;
import java.time.Clock;
@@ -69,9 +65,7 @@
* Provides core data transfer services to the system.
*/
@CoreExtension
-@Provides({ StatusCheckerRegistry.class, ResourceManifestGenerator.class, TransferProcessManager.class,
- TransferProcessObservable.class, DataFlowManager.class, ProvisionManager.class,
- EndpointDataReferenceReceiverRegistry.class })
+@Provides({ TransferProcessManager.class, EndpointDataReferenceReceiverRegistry.class })
@Extension(value = TransferCoreExtension.NAME)
public class TransferCoreExtension implements ServiceExtension {
@@ -97,6 +91,21 @@ public class TransferCoreExtension implements ServiceExtension {
@Inject
private TransferProcessStore transferProcessStore;
+ @Inject
+ private DataFlowManager dataFlowManager;
+
+ @Inject
+ private StatusCheckerRegistry statusCheckerRegistry;
+
+ @Inject
+ private ResourceManifestGenerator resourceManifestGenerator;
+
+ @Inject
+ private ProvisionManager provisionManager;
+
+ @Inject
+ private TransferProcessObservable observable;
+
@Inject
private PolicyArchive policyArchive;
@@ -118,9 +127,6 @@ public class TransferCoreExtension implements ServiceExtension {
@Inject
private Clock clock;
- @Inject
- private PolicyEngine policyEngine;
-
@Inject
private TypeManager typeManager;
@@ -133,6 +139,9 @@ public class TransferCoreExtension implements ServiceExtension {
@Inject
private ProtocolWebhook protocolWebhook;
+ @Inject
+ private TransferProcessPendingGuard pendingGuard;
+
private TransferProcessManagerImpl processManager;
@Override
@@ -146,18 +155,6 @@ public void initialize(ServiceExtensionContext context) {
registerTypes(typeManager);
- var dataFlowManager = new DataFlowManagerImpl();
- context.registerService(DataFlowManager.class, dataFlowManager);
-
- var manifestGenerator = new ResourceManifestGeneratorImpl(policyEngine);
- context.registerService(ResourceManifestGenerator.class, manifestGenerator);
-
- var statusCheckerRegistry = new StatusCheckerRegistryImpl();
- context.registerService(StatusCheckerRegistry.class, statusCheckerRegistry);
-
- var provisionManager = new ProvisionManagerImpl(monitor);
- context.registerService(ProvisionManager.class, provisionManager);
-
var iterationWaitMillis = context.getSetting(TRANSFER_STATE_MACHINE_ITERATION_WAIT_MILLIS, DEFAULT_ITERATION_WAIT);
var waitStrategy = context.hasService(TransferWaitStrategy.class) ? context.getService(TransferWaitStrategy.class) : new ExponentialWaitStrategy(iterationWaitMillis);
@@ -166,24 +163,17 @@ public void initialize(ServiceExtensionContext context) {
var endpointDataReferenceReceiverRegistry = new EndpointDataReferenceReceiverRegistryImpl(typeTransformerRegistry);
context.registerService(EndpointDataReferenceReceiverRegistry.class, endpointDataReferenceReceiverRegistry);
-
- // Integration with the new DSP protocol
eventRouter.register(TransferProcessStarted.class, endpointDataReferenceReceiverRegistry);
- var observable = new TransferProcessObservableImpl();
- context.registerService(TransferProcessObservable.class, observable);
-
observable.registerListener(new TransferProcessEventListener(eventRouter, clock));
- var retryLimit = context.getSetting(TRANSFER_SEND_RETRY_LIMIT, DEFAULT_SEND_RETRY_LIMIT);
- var retryBaseDelay = context.getSetting(TRANSFER_SEND_RETRY_BASE_DELAY_MS, DEFAULT_SEND_RETRY_BASE_DELAY);
- var entityRetryProcessConfiguration = new EntityRetryProcessConfiguration(retryLimit, () -> new ExponentialWaitStrategy(retryBaseDelay));
+ var entityRetryProcessConfiguration = getEntityRetryProcessConfiguration(context);
var provisionResponsesHandler = new ProvisionResponsesHandler(observable, monitor, vault, typeManager);
var deprovisionResponsesHandler = new DeprovisionResponsesHandler(observable, monitor, vault);
processManager = TransferProcessManagerImpl.Builder.newInstance()
.waitStrategy(waitStrategy)
- .manifestGenerator(manifestGenerator)
+ .manifestGenerator(resourceManifestGenerator)
.dataFlowManager(dataFlowManager)
.provisionManager(provisionManager)
.dispatcherRegistry(dispatcherRegistry)
@@ -202,6 +192,7 @@ public void initialize(ServiceExtensionContext context) {
.protocolWebhook(protocolWebhook)
.provisionResponsesHandler(provisionResponsesHandler)
.deprovisionResponsesHandler(deprovisionResponsesHandler)
+ .pendingGuard(pendingGuard)
.build();
context.registerService(TransferProcessManager.class, processManager);
@@ -222,6 +213,13 @@ public void shutdown() {
}
}
+ @NotNull
+ private EntityRetryProcessConfiguration getEntityRetryProcessConfiguration(ServiceExtensionContext context) {
+ var retryLimit = context.getSetting(TRANSFER_SEND_RETRY_LIMIT, DEFAULT_SEND_RETRY_LIMIT);
+ var retryBaseDelay = context.getSetting(TRANSFER_SEND_RETRY_BASE_DELAY_MS, DEFAULT_SEND_RETRY_BASE_DELAY);
+ return new EntityRetryProcessConfiguration(retryLimit, () -> new ExponentialWaitStrategy(retryBaseDelay));
+ }
+
private void registerTypes(TypeManager typeManager) {
typeManager.registerTypes(DataRequest.class);
typeManager.registerTypes(ProvisionedContentResource.class);
diff --git a/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferProcessDefaultServicesExtension.java b/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferProcessDefaultServicesExtension.java
new file mode 100644
index 00000000000..1705313a4d1
--- /dev/null
+++ b/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferProcessDefaultServicesExtension.java
@@ -0,0 +1,78 @@
+/*
+ * 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.edc.connector.transfer;
+
+import org.eclipse.edc.connector.transfer.flow.DataFlowManagerImpl;
+import org.eclipse.edc.connector.transfer.observe.TransferProcessObservableImpl;
+import org.eclipse.edc.connector.transfer.process.StatusCheckerRegistryImpl;
+import org.eclipse.edc.connector.transfer.provision.ProvisionManagerImpl;
+import org.eclipse.edc.connector.transfer.provision.ResourceManifestGeneratorImpl;
+import org.eclipse.edc.connector.transfer.spi.TransferProcessPendingGuard;
+import org.eclipse.edc.connector.transfer.spi.flow.DataFlowManager;
+import org.eclipse.edc.connector.transfer.spi.observe.TransferProcessObservable;
+import org.eclipse.edc.connector.transfer.spi.provision.ProvisionManager;
+import org.eclipse.edc.connector.transfer.spi.provision.ResourceManifestGenerator;
+import org.eclipse.edc.connector.transfer.spi.status.StatusCheckerRegistry;
+import org.eclipse.edc.policy.engine.spi.PolicyEngine;
+import org.eclipse.edc.runtime.metamodel.annotation.Extension;
+import org.eclipse.edc.runtime.metamodel.annotation.Inject;
+import org.eclipse.edc.runtime.metamodel.annotation.Provider;
+import org.eclipse.edc.spi.system.ServiceExtension;
+import org.eclipse.edc.spi.system.ServiceExtensionContext;
+
+@Extension(value = TransferProcessDefaultServicesExtension.NAME)
+public class TransferProcessDefaultServicesExtension implements ServiceExtension {
+
+ public static final String NAME = "Transfer Process Default Services";
+
+ @Inject
+ private PolicyEngine policyEngine;
+
+ @Override
+ public String name() {
+ return NAME;
+ }
+
+ @Provider
+ public DataFlowManager dataFlowManager() {
+ return new DataFlowManagerImpl();
+ }
+
+ @Provider
+ public ResourceManifestGenerator resourceManifestGenerator() {
+ return new ResourceManifestGeneratorImpl(policyEngine);
+ }
+
+ @Provider
+ public StatusCheckerRegistry statusCheckerRegistry() {
+ return new StatusCheckerRegistryImpl();
+ }
+
+ @Provider
+ public ProvisionManager provisionManager(ServiceExtensionContext context) {
+ return new ProvisionManagerImpl(context.getMonitor());
+ }
+
+ @Provider
+ public TransferProcessObservable transferProcessObservable() {
+ return new TransferProcessObservableImpl();
+ }
+
+ @Provider(isDefault = true)
+ public TransferProcessPendingGuard pendingGuard() {
+ return it -> false;
+ }
+
+}
diff --git a/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImpl.java b/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImpl.java
index a01f21ebb6a..9dd7683e7fa 100644
--- a/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImpl.java
+++ b/core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImpl.java
@@ -21,6 +21,7 @@
import org.eclipse.edc.connector.transfer.provision.DeprovisionResponsesHandler;
import org.eclipse.edc.connector.transfer.provision.ProvisionResponsesHandler;
import org.eclipse.edc.connector.transfer.spi.TransferProcessManager;
+import org.eclipse.edc.connector.transfer.spi.TransferProcessPendingGuard;
import org.eclipse.edc.connector.transfer.spi.flow.DataFlowManager;
import org.eclipse.edc.connector.transfer.spi.observe.TransferProcessObservable;
import org.eclipse.edc.connector.transfer.spi.observe.TransferProcessStartedData;
@@ -45,6 +46,7 @@
import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.protocol.ProtocolWebhook;
+import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.response.StatusResult;
import org.eclipse.edc.spi.retry.ExponentialWaitStrategy;
import org.eclipse.edc.spi.retry.WaitStrategy;
@@ -52,8 +54,9 @@
import org.eclipse.edc.spi.system.ExecutorInstrumentation;
import org.eclipse.edc.spi.telemetry.Telemetry;
import org.eclipse.edc.spi.types.domain.DataAddress;
+import org.eclipse.edc.statemachine.Processor;
+import org.eclipse.edc.statemachine.ProcessorImpl;
import org.eclipse.edc.statemachine.StateMachineManager;
-import org.eclipse.edc.statemachine.StateProcessorImpl;
import org.eclipse.edc.statemachine.retry.EntityRetryProcessConfiguration;
import org.eclipse.edc.statemachine.retry.EntityRetryProcessFactory;
import org.jetbrains.annotations.NotNull;
@@ -83,6 +86,7 @@
import static org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates.STARTING;
import static org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates.TERMINATING;
import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState;
+import static org.eclipse.edc.spi.persistence.StateEntityStore.isNotPending;
import static org.eclipse.edc.spi.types.domain.DataAddress.EDC_DATA_ADDRESS_SECRET;
/**
@@ -129,6 +133,7 @@ public class TransferProcessManagerImpl implements TransferProcessManager {
private ProtocolWebhook protocolWebhook;
private ProvisionResponsesHandler provisionResponsesHandler;
private DeprovisionResponsesHandler deprovisionResponsesHandler;
+ private TransferProcessPendingGuard pendingGuard = tp -> false;
private TransferProcessManagerImpl() {
}
@@ -520,9 +525,18 @@ private boolean processDeprovisioning(TransferProcess process) {
.execute("deprovisioning");
}
- private StateProcessorImpl processTransfersInState(TransferProcessStates state, Function function) {
- var functionWithTraceContext = telemetry.contextPropagationMiddleware(function);
- return new StateProcessorImpl<>(() -> transferProcessStore.nextNotLeased(batchSize, hasState(state.code())), functionWithTraceContext);
+ private Processor processTransfersInState(TransferProcessStates state, Function function) {
+ var filter = new Criterion[] { hasState(state.code()), isNotPending() };
+ return ProcessorImpl.Builder.newInstance(() -> transferProcessStore.nextNotLeased(batchSize, filter))
+ .process(telemetry.contextPropagationMiddleware(function))
+ .guard(pendingGuard, this::setPending)
+ .build();
+ }
+
+ private boolean setPending(TransferProcess transferProcess) {
+ transferProcess.setPending(true);
+ update(transferProcess);
+ return true;
}
private void transitionToProvisioning(TransferProcess process) {
@@ -732,6 +746,11 @@ public Builder deprovisionResponsesHandler(DeprovisionResponsesHandler deprovisi
return this;
}
+ public Builder pendingGuard(TransferProcessPendingGuard pendingGuard) {
+ manager.pendingGuard = pendingGuard;
+ return this;
+ }
+
public TransferProcessManagerImpl build() {
Objects.requireNonNull(manager.manifestGenerator, "manifestGenerator cannot be null");
Objects.requireNonNull(manager.provisionManager, "provisionManager cannot be null");
diff --git a/core/control-plane/transfer-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/core/control-plane/transfer-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
index 03668de5575..65bd1b5ffe3 100644
--- a/core/control-plane/transfer-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
+++ b/core/control-plane/transfer-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
@@ -14,4 +14,5 @@
org.eclipse.edc.connector.transfer.TransferCoreExtension
org.eclipse.edc.connector.transfer.TransferProcessCommandExtension
+org.eclipse.edc.connector.transfer.TransferProcessDefaultServicesExtension
diff --git a/core/control-plane/transfer-core/src/test/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImplTest.java b/core/control-plane/transfer-core/src/test/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImplTest.java
index cda9966968b..c16e12cba40 100644
--- a/core/control-plane/transfer-core/src/test/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImplTest.java
+++ b/core/control-plane/transfer-core/src/test/java/org/eclipse/edc/connector/transfer/process/TransferProcessManagerImplTest.java
@@ -17,13 +17,13 @@
package org.eclipse.edc.connector.transfer.process;
import org.eclipse.edc.connector.policy.spi.store.PolicyArchive;
-import org.eclipse.edc.connector.transfer.TestProvisionedContentResource;
import org.eclipse.edc.connector.transfer.TestProvisionedDataDestinationResource;
import org.eclipse.edc.connector.transfer.TestResourceDefinition;
import org.eclipse.edc.connector.transfer.TokenTestProvisionResource;
import org.eclipse.edc.connector.transfer.observe.TransferProcessObservableImpl;
import org.eclipse.edc.connector.transfer.provision.DeprovisionResponsesHandler;
import org.eclipse.edc.connector.transfer.provision.ProvisionResponsesHandler;
+import org.eclipse.edc.connector.transfer.spi.TransferProcessPendingGuard;
import org.eclipse.edc.connector.transfer.spi.flow.DataFlowManager;
import org.eclipse.edc.connector.transfer.spi.observe.TransferProcessListener;
import org.eclipse.edc.connector.transfer.spi.provision.ProvisionManager;
@@ -48,7 +48,6 @@
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.asset.DataAddressResolver;
import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry;
-import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.protocol.ProtocolWebhook;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.response.ResponseStatus;
@@ -96,6 +95,7 @@
import static org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates.TERMINATED;
import static org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates.TERMINATING;
import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState;
+import static org.eclipse.edc.spi.persistence.StateEntityStore.isNotPending;
import static org.eclipse.edc.spi.response.ResponseStatus.FATAL_ERROR;
import static org.eclipse.edc.spi.types.domain.DataAddress.EDC_DATA_ADDRESS_SECRET;
import static org.mockito.AdditionalMatchers.aryEq;
@@ -139,6 +139,7 @@ class TransferProcessManagerImplTest {
private final ProvisionResponsesHandler provisionResponsesHandler = mock();
private final DeprovisionResponsesHandler deprovisionResponsesHandler = mock();
private final String protocolWebhookUrl = "http://protocol.webhook/url";
+ private final TransferProcessPendingGuard pendingGuard = mock();
private TransferProcessManagerImpl manager;
@@ -156,7 +157,7 @@ void setup() {
.batchSize(TRANSFER_MANAGER_BATCHSIZE)
.dispatcherRegistry(dispatcherRegistry)
.manifestGenerator(manifestGenerator)
- .monitor(mock(Monitor.class))
+ .monitor(mock())
.clock(clock)
.statusCheckerRegistry(statusCheckerRegistry)
.observable(observable)
@@ -168,6 +169,7 @@ void setup() {
.protocolWebhook(protocolWebhook)
.provisionResponsesHandler(provisionResponsesHandler)
.deprovisionResponsesHandler(deprovisionResponsesHandler)
+ .pendingGuard(pendingGuard)
.build();
}
@@ -716,6 +718,23 @@ void deprovisioning_shouldNotInvokePostActions_whenResponsesHandlerCannotHandle(
});
}
+ @Test
+ void pendingGuard_shouldSetTheTransferPending_whenPendingGuardMatches() {
+ when(pendingGuard.test(any())).thenReturn(true);
+ var process = createTransferProcessBuilder(STARTING).build();
+ when(transferProcessStore.nextNotLeased(anyInt(), stateIs(STARTING.code()))).thenReturn(List.of(process)).thenReturn(emptyList());
+
+ manager.start();
+
+ await().untilAsserted(() -> {
+ var captor = ArgumentCaptor.forClass(TransferProcess.class);
+ verify(transferProcessStore).save(captor.capture());
+ var saved = captor.getValue();
+ assertThat(saved.getState()).isEqualTo(STARTING.code());
+ assertThat(saved.isPending()).isTrue();
+ });
+ }
+
@ParameterizedTest
@ArgumentsSource(DispatchFailureArguments.class)
void dispatchFailure(TransferProcessStates starting, TransferProcessStates ending, CompletableFuture> result, UnaryOperator builderEnricher) {
@@ -737,18 +756,7 @@ void dispatchFailure(TransferProcessStates starting, TransferProcessStates endin
}
private Criterion[] stateIs(int state) {
- return aryEq(new Criterion[]{ hasState(state) });
- }
-
- private TestProvisionedContentResource createTestProvisionedContentResource(String resourceDefinitionId) {
- return TestProvisionedContentResource.Builder.newInstance()
- .resourceName("test")
- .id("1")
- .transferProcessId("2")
- .resourceDefinitionId(resourceDefinitionId)
- .dataAddress(DataAddress.Builder.newInstance().type("test").build())
- .hasToken(true)
- .build();
+ return aryEq(new Criterion[]{ hasState(state), isNotPending() });
}
private DataFlowResponse createDataFlowResponse() {
diff --git a/docs/developer/state-machine.md b/docs/developer/state-machine.md
index d335a8e51f1..7987ecd83ad 100644
--- a/docs/developer/state-machine.md
+++ b/docs/developer/state-machine.md
@@ -6,65 +6,102 @@ The framework currently manages a single execution thread.
## Collaborators
-- The class which defines state machine instances. The `StatefulEntity` base class can be used to derive state machine entity classes. For example, a `ContractNegotiation` is a `StatefulEntity` for an EDC contract negotiation.
-- The `StateMachineManager` which manages an execution thread that periodically gives a chance to state machines to progress their state.
-- The state-machine specific Manager which instantiates the `StateMachineManager` and defines processors for each state for a given state machine class. For example, `ConsumerContractNegotiationManagerImpl` manages `ContractNegotiation`s in which the connector is a consumer.
+- The class which defines state machine instances. The `StatefulEntity` base class can be used to derive state machine
+ entity classes. For example, a `ContractNegotiation` is a `StatefulEntity` for an EDC contract negotiation.
+- The `StateMachineManager` which manages an execution thread that periodically gives a chance to state machines to
+ progress their state.
+- The state-machine specific Manager which instantiates the `StateMachineManager` and defines processors for each state
+ for a given state machine class. For example, `ConsumerContractNegotiationManagerImpl` manages `ContractNegotiation`s
+ in which the connector is a consumer.
- The `ServiceExtension` which manages the Manager's lifecycle.
-- The Store which manages `StatefulEntity` persistence. `InMemoryStatefulEntityStore` provides a utility class to back in-memory implementations for testing.
+- The Store which manages `StatefulEntity` persistence. `InMemoryStatefulEntityStore` provides a utility class to back
+ in-memory implementations for testing.
## State-machine specific Manager
-The Manager manages the `StateMachineManager`'s lifecycle and defines the state machine's behavior, while the `StatefulEntity` is only concerned with the state machine's data.
+The Manager manages the `StateMachineManager`'s lifecycle and defines the state machine's behavior, while the `StatefulEntity`
+is only concerned with the state machine's data.
Here's a prototypical Manager implementation:
```java
-public void start() { // Called from ServiceExtension start() method
- stateMachineManager = StateMachineManager.Builder.newInstance("state-machine-name", ...)
- // Processors for non-terminal states
- .processor(processEntitiesInState(STATE1, this::processState1))
- .processor(processEntitiesInState(STATE2, this::processState2))
- .processor(processEntitiesInState(STATE3, this::processState3))
- .processor(onCommands(this::processCommand))
- .build();
- stateMachineManager.start();
-}
+public class EntityManager {
+
+ public void start() { // Called from ServiceExtension start() method
+ stateMachineManager = StateMachineManager.Builder.newInstance("state-machine-name", monitor, executorInstrumentation, waitStrategy)
+ // Processors for non-terminal states
+ .processor(processEntitiesInState(STATE1, this::processState1))
+ .processor(processEntitiesInState(STATE2, this::processState2))
+ .processor(processEntitiesInState(STATE3, this::processState3))
+ .build();
+
+ stateMachineManager.start();
+ }
-public void stop() { // Called from ServiceExtension shutdown() method
- stateMachineManager.stop();
-}
+ public void stop() { // Called from ServiceExtension shutdown() method
+ if (stateMachineManager != null) {
+ stateMachineManager.stop();
+ }
+ }
+
+ private Processor processEntitiesInState(State state, Function function) {
+ var filter = new Criterion[] { hasState(state.code()), isNotPending() };
+ return ProcessorImpl.Builder.newInstance(() -> transferProcessStore.nextNotLeased(batchSize, filter))
+ .process(telemetry.contextPropagationMiddleware(function))
+ .guard(pendingGuard, this::setPending) // a guard can be added to, e.g. put in pending certain entities based on the `pendingGuard` predicate
+ .build();
+ }
+
+ // Processor functions should return true only if the state machine has been updated
+ private boolean processState1(StatefulEntityImpl sm) {
+ if (conditionsForTransitionFromState1ToState2Apply(sm)) {
+ sm.transitionState2();
+ store.save(sm);
+ return true;
+ }
+ return false;
+ }
-private StateProcessorImpl processEntitiesInState(State state, Function function) {
- return new StateProcessorImpl<>(() -> store.nextNotLeased(batchSize, hasState(state.code())), function);
}
+```
-// Processor functions should return true only if the state machine has been updated
-private boolean processState1(StatefulEntityImpl sm) {
- if (conditionsForTransitionFromState1ToState2Apply(sm)) {
- sm.transitionState2();
- store.save(sm);
- return true;
+### Guards
+On a state machine `Processor` a `Guard` can be specified. that's a way to have a custom flow based on a predicate that can
+be extended, for example, to enable "external interactions" in the state machine, as user interactions. A `Guard` predicate
+can be set on the processor with a specific process to be executed. This way when the predicate matches the entity can be
+set to pending, making it "invisible" for the state machine, but still accessible and modifiable by users or external systems.
+
+`Guard` example:
+```java
+class EntityPendingGuard implements PendingGuard {
+
+ // custom collaborators as other services
+
+ boolean test(Entity entity) {
+ // additional logic
+ return entity.getState() = SPECIFIC_STATE.code() && otherCondition; // if true, the entity will be set as pending
}
- return false;
+
}
-...
```
## State-machine store
-The Store which manages `StatefulEntity` persistence must persist entities in a storage system. In-memory implementations are provided for testing.
+The Store which manages `StatefulEntity` persistence must persist entities in a storage system. In-memory implementations
+are provided for testing.
-Stores using persistent implementations must manage leases to support EDC clustered deployment. This ensures an entity is processed by only one EDC instance at a time (assuming processing is quicker than lease expiration).
+Stores using persistent implementations must manage leases to support EDC clustered deployment. This ensures an entity is
+processed by only one EDC instance at a time (assuming processing is quicker than lease expiration).
```java
-public void save(StatefulEntityImpl instance) {
- // persist instance
- // release lease
-}
-
-List nextNotLeased(int max, Criterion... criteria); {
- // retrieve and lease at most limit instances that satisfy criteria
+class EntityStore {
+ public void save(StatefulEntityImpl instance) {
+ // persist instance
+ // release lease
+ }
+
+ List nextNotLeased(int max, Criterion... criteria); {
+ // retrieve and lease at most limit instances that satisfy criteria
+ }
}
-
-
```
diff --git a/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/ColumnEntry.java b/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/ColumnEntry.java
new file mode 100644
index 00000000000..0e59c7ca515
--- /dev/null
+++ b/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/ColumnEntry.java
@@ -0,0 +1,36 @@
+/*
+ * 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.edc.sql.statement;
+
+/**
+ * Represent a column and its value into a JDBC prepared statement.
+ *
+ * @param columnName the name of the column.
+ * @param value the value of the column, by default it will be '?' but it could be different.
+ */
+public record ColumnEntry(String columnName, String value) {
+
+ public static ColumnEntry standardColumn(String columnName) {
+ return new ColumnEntry(columnName, "?");
+ }
+
+ public static ColumnEntry jsonColumn(String columnName, String jsonOperator) {
+ return new ColumnEntry(columnName, "?" + jsonOperator);
+ }
+
+ public ColumnEntry append(ColumnEntry other) {
+ return new ColumnEntry(columnName + ", " + other.columnName, value + ", " + other.value);
+ }
+}
diff --git a/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/SqlExecuteStatement.java b/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/SqlExecuteStatement.java
new file mode 100644
index 00000000000..c242103910a
--- /dev/null
+++ b/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/SqlExecuteStatement.java
@@ -0,0 +1,106 @@
+/*
+ * 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.edc.sql.statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.joining;
+import static org.eclipse.edc.sql.statement.ColumnEntry.standardColumn;
+
+/**
+ * Helps in constructing SQL insert/update statements without having to deal with the SQL syntax directly
+ */
+public class SqlExecuteStatement {
+
+ private final List columnEntries = new ArrayList<>();
+ private final String jsonCastOperator;
+
+ public SqlExecuteStatement(String jsonCastOperator) {
+ this.jsonCastOperator = jsonCastOperator;
+ }
+
+ /**
+ * Create a new instance with the JSON cast operator
+ *
+ * @param jsonCastOperator the json cast operator.
+ * @return a new {@link SqlExecuteStatement}.
+ */
+ public static SqlExecuteStatement newInstance(String jsonCastOperator) {
+ return new SqlExecuteStatement(jsonCastOperator);
+ }
+
+ /**
+ * Add a new columnEntry
+ *
+ * @param columnEntry the columnEntry.
+ * @return the {@link SqlExecuteStatement}.
+ */
+ public SqlExecuteStatement add(ColumnEntry columnEntry) {
+ columnEntries.add(columnEntry);
+ return this;
+ }
+
+ /**
+ * Add a new standard column
+ *
+ * @param columnName the column name.
+ * @return the {@link SqlExecuteStatement}.
+ */
+ public SqlExecuteStatement column(String columnName) {
+ columnEntries.add(standardColumn(columnName));
+ return this;
+ }
+
+ /**
+ * Add a new json column
+ *
+ * @param columnName the column name.
+ * @return the {@link SqlExecuteStatement}.
+ */
+ public SqlExecuteStatement jsonColumn(String columnName) {
+ columnEntries.add(ColumnEntry.jsonColumn(columnName, jsonCastOperator));
+ return this;
+ }
+
+ /**
+ * Gives a SQL insert statement.
+ *
+ * @param tableName the table name.
+ * @return sql insert statement.
+ */
+ public String insertInto(String tableName) {
+ var columnValues = columnEntries.stream().reduce(ColumnEntry::append).orElseThrow();
+
+ return format("INSERT INTO %s (%s) VALUES (%s);", tableName, columnValues.columnName(), columnValues.value());
+ }
+
+
+ /**
+ * Gives a SQL update statement.
+ *
+ * @param tableName the table name.
+ * @param where the update field condition
+ * @return sql update statement.
+ */
+ public String update(String tableName, ColumnEntry where) {
+ var statement = columnEntries.stream()
+ .map(it -> it.columnName() + "=" + it.value())
+ .collect(joining(","));
+
+ return format("UPDATE %s SET %s WHERE %s=%s", tableName, statement, where.columnName(), where.value());
+ }
+}
diff --git a/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/api/client/transferprocess/TransferProcessHttpClientIntegrationTest.java b/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/api/client/transferprocess/TransferProcessHttpClientIntegrationTest.java
index d280a17bf47..f5a6f69d145 100644
--- a/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/api/client/transferprocess/TransferProcessHttpClientIntegrationTest.java
+++ b/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/api/client/transferprocess/TransferProcessHttpClientIntegrationTest.java
@@ -83,7 +83,7 @@ void setUp(EdcExtension extension) {
void shouldCallTransferProcessApiWithComplete(TransferProcessStore store, DataPlaneManager manager, ControlPlaneApiUrl callbackUrl) {
when(service.transfer(any())).thenReturn(completedFuture(StreamResult.success()));
var id = "tp-id";
- store.updateOrCreate(createTransferProcess(id));
+ store.save(createTransferProcess(id));
var dataFlowRequest = createDataFlowRequest(id, callbackUrl.get());
manager.initiateTransfer(dataFlowRequest);
@@ -99,7 +99,7 @@ void shouldCallTransferProcessApiWithComplete(TransferProcessStore store, DataPl
void shouldCallTransferProcessApiWithFailed(TransferProcessStore store, DataPlaneManager manager, ControlPlaneApiUrl callbackUrl) {
when(service.transfer(any())).thenReturn(completedFuture(StreamResult.error("error")));
var id = "tp-id";
- store.updateOrCreate(createTransferProcess(id));
+ store.save(createTransferProcess(id));
var dataFlowRequest = createDataFlowRequest(id, callbackUrl.get());
manager.initiateTransfer(dataFlowRequest);
@@ -117,7 +117,7 @@ void shouldCallTransferProcessApiWithFailed(TransferProcessStore store, DataPlan
void shouldCallTransferProcessApiWithException(TransferProcessStore store, DataPlaneManager manager, ControlPlaneApiUrl callbackUrl) {
when(service.transfer(any())).thenReturn(failedFuture(new EdcException("error")));
var id = "tp-id";
- store.updateOrCreate(createTransferProcess(id));
+ store.save(createTransferProcess(id));
var dataFlowRequest = createDataFlowRequest(id, callbackUrl.get());
manager.initiateTransfer(dataFlowRequest);
diff --git a/extensions/control-plane/store/sql/contract-negotiation-store-sql/docs/schema.sql b/extensions/control-plane/store/sql/contract-negotiation-store-sql/docs/schema.sql
index 24570850070..0fabd673e85 100644
--- a/extensions/control-plane/store/sql/contract-negotiation-store-sql/docs/schema.sql
+++ b/extensions/control-plane/store/sql/contract-negotiation-store-sql/docs/schema.sql
@@ -37,17 +37,17 @@ CREATE TABLE IF NOT EXISTS edc_contract_agreement
CREATE TABLE IF NOT EXISTS edc_contract_negotiation
(
- id VARCHAR NOT NULL
+ id VARCHAR NOT NULL
CONSTRAINT contract_negotiation_pk
PRIMARY KEY,
- created_at BIGINT NOT NULL,
- updated_at BIGINT NOT NULL,
+ created_at BIGINT NOT NULL,
+ updated_at BIGINT NOT NULL,
correlation_id VARCHAR,
- counterparty_id VARCHAR NOT NULL,
- counterparty_address VARCHAR NOT NULL,
- protocol VARCHAR NOT NULL,
- type VARCHAR NOT NULL,
- state INTEGER DEFAULT 0 NOT NULL,
+ counterparty_id VARCHAR NOT NULL,
+ counterparty_address VARCHAR NOT NULL,
+ protocol VARCHAR NOT NULL,
+ type VARCHAR NOT NULL,
+ state INTEGER DEFAULT 0 NOT NULL,
state_count INTEGER DEFAULT 0,
state_timestamp BIGINT,
error_detail VARCHAR,
@@ -57,6 +57,7 @@ CREATE TABLE IF NOT EXISTS edc_contract_negotiation
contract_offers JSON,
callback_addresses JSON,
trace_context JSON,
+ pending BOOLEAN DEFAULT FALSE,
lease_id VARCHAR
CONSTRAINT contract_negotiation_lease_lease_id_fk
REFERENCES edc_lease
diff --git a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/SqlContractNegotiationStore.java b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/SqlContractNegotiationStore.java
index 96cd7298ba5..d2f14117e8f 100644
--- a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/SqlContractNegotiationStore.java
+++ b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/SqlContractNegotiationStore.java
@@ -172,7 +172,7 @@ public void delete(String negotiationId) {
@Override
public @NotNull List nextNotLeased(int max, Criterion... criteria) {
return transactionContext.execute(() -> {
- var filter = Arrays.stream(criteria).collect(toList());
+ var filter = Arrays.stream(criteria).toList();
var querySpec = QuerySpec.Builder.newInstance().filter(filter).limit(max).build();
var statement = statements.createNegotiationsQuery(querySpec);
statement.addWhereClause(statements.getNotLeasedFilter());
@@ -269,6 +269,7 @@ private void update(Connection connection, String negotiationId, ContractNegotia
toJson(updatedValues.getTraceContext()),
ofNullable(updatedValues.getContractAgreement()).map(ContractAgreement::getId).orElse(null),
updatedValues.getUpdatedAt(),
+ updatedValues.isPending(),
negotiationId);
}
@@ -281,7 +282,8 @@ private void insert(Connection connection, ContractNegotiation negotiation) {
}
var stmt = statements.getInsertNegotiationTemplate();
- queryExecutor.execute(connection, stmt, negotiation.getId(),
+ queryExecutor.execute(connection, stmt,
+ negotiation.getId(),
negotiation.getCorrelationId(),
negotiation.getCounterPartyId(),
negotiation.getCounterPartyAddress(),
@@ -296,9 +298,8 @@ private void insert(Connection connection, ContractNegotiation negotiation) {
toJson(negotiation.getCallbackAddresses()),
toJson(negotiation.getTraceContext()),
negotiation.getCreatedAt(),
- negotiation.getUpdatedAt());
-
-
+ negotiation.getUpdatedAt(),
+ negotiation.isPending());
}
private void upsertAgreement(ContractAgreement contractAgreement) {
@@ -396,6 +397,7 @@ private ContractNegotiation mapContractNegotiation(ResultSet resultSet, ResultSe
.type(ContractNegotiation.Type.valueOf(resultSet.getString(statements.getTypeColumn())))
.createdAt(resultSet.getLong(statements.getCreatedAtColumn()))
.updatedAt(resultSet.getLong(statements.getUpdatedAtColumn()))
+ .pending(resultSet.getBoolean(statements.getPendingColumn()))
.build();
}
diff --git a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/BaseSqlDialectStatements.java b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/BaseSqlDialectStatements.java
index d5dbddc9eab..165392de3a9 100644
--- a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/BaseSqlDialectStatements.java
+++ b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/BaseSqlDialectStatements.java
@@ -16,6 +16,8 @@
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.sql.dialect.BaseSqlDialect;
+import org.eclipse.edc.sql.statement.ColumnEntry;
+import org.eclipse.edc.sql.statement.SqlExecuteStatement;
import org.eclipse.edc.sql.translation.SqlQueryStatement;
import static java.lang.String.format;
@@ -38,20 +40,41 @@ public String getFindContractAgreementTemplate() {
@Override
public String getUpdateNegotiationTemplate() {
- return format("UPDATE %s SET %s=?, %s=?, %s=?, %s=?, %s=?%s, %s=?%s, %s=?%s, %s=?, %s=? WHERE id = ?;",
- getContractNegotiationTable(), getStateColumn(), getStateCountColumn(), getStateTimestampColumn(),
- getErrorDetailColumn(), getContractOffersColumn(), getFormatJsonOperator(), getCallbackAddressesColumn(), getFormatJsonOperator(), getTraceContextColumn(),
- getFormatJsonOperator(), getContractAgreementIdFkColumn(), getUpdatedAtColumn());
+ return SqlExecuteStatement.newInstance(getFormatJsonOperator())
+ .column(getStateColumn())
+ .column(getStateCountColumn())
+ .column(getStateTimestampColumn())
+ .column(getErrorDetailColumn())
+ .jsonColumn(getContractOffersColumn())
+ .jsonColumn(getCallbackAddressesColumn())
+ .jsonColumn(getTraceContextColumn())
+ .column(getContractAgreementIdFkColumn())
+ .column(getUpdatedAtColumn())
+ .column(getPendingColumn())
+ .update(getContractNegotiationTable(), ColumnEntry.standardColumn(getIdColumn()));
}
@Override
public String getInsertNegotiationTemplate() {
- return format("INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)\n" +
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?%s, ?%s, ?%s, ?, ?); ",
- getContractNegotiationTable(), getIdColumn(), getCorrelationIdColumn(), getCounterPartyIdColumn(), getCounterPartyAddressColumn(), getTypeColumn(), getProtocolColumn(), getStateColumn(), getStateCountColumn(),
- getStateTimestampColumn(), getErrorDetailColumn(), getContractAgreementIdFkColumn(), getContractOffersColumn(), getCallbackAddressesColumn(), getTraceContextColumn(), getCreatedAtColumn(), getUpdatedAtColumn(),
- getFormatJsonOperator(), getFormatJsonOperator(), getFormatJsonOperator()
- );
+ return SqlExecuteStatement.newInstance(getFormatJsonOperator())
+ .column(getIdColumn())
+ .column(getCorrelationIdColumn())
+ .column(getCounterPartyIdColumn())
+ .column(getCounterPartyAddressColumn())
+ .column(getTypeColumn())
+ .column(getProtocolColumn())
+ .column(getStateColumn())
+ .column(getStateCountColumn())
+ .column(getStateTimestampColumn())
+ .column(getErrorDetailColumn())
+ .column(getContractAgreementIdFkColumn())
+ .jsonColumn(getContractOffersColumn())
+ .jsonColumn(getCallbackAddressesColumn())
+ .jsonColumn(getTraceContextColumn())
+ .column(getCreatedAtColumn())
+ .column(getUpdatedAtColumn())
+ .column(getPendingColumn())
+ .insertInto(getContractNegotiationTable());
}
@Override
diff --git a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/ContractNegotiationStatements.java b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/ContractNegotiationStatements.java
index 7fe27a52c49..cffb0239c7b 100644
--- a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/ContractNegotiationStatements.java
+++ b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/ContractNegotiationStatements.java
@@ -137,6 +137,10 @@ default String getUpdatedAtColumn() {
return "updated_at";
}
+ default String getPendingColumn() {
+ return "pending";
+ }
+
SqlQueryStatement createNegotiationsQuery(QuerySpec querySpec);
SqlQueryStatement createAgreementsQuery(QuerySpec querySpec);
diff --git a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/postgres/ContractNegotiationMapping.java b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/postgres/ContractNegotiationMapping.java
index 2682e0b355e..dee4db397fe 100644
--- a/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/postgres/ContractNegotiationMapping.java
+++ b/extensions/control-plane/store/sql/contract-negotiation-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/contractnegotiation/store/schema/postgres/ContractNegotiationMapping.java
@@ -35,6 +35,7 @@ class ContractNegotiationMapping extends TranslationMapping {
private static final String FIELD_ERRORDETAIL = "errorDetail";
private static final String FIELD_CONTRACT_AGREEMENT = "contractAgreement";
private static final String FIELD_TRACECONTEXT = "traceContext";
+ private static final String FIELD_PENDING = "pending";
ContractNegotiationMapping(ContractNegotiationStatements statements) {
@@ -47,9 +48,9 @@ class ContractNegotiationMapping extends TranslationMapping {
add(FIELD_TYPE, statements.getTypeColumn());
add(FIELD_STATE, statements.getStateColumn());
add(FIELD_STATECOUNT, statements.getStateCountColumn());
-
add(FIELD_STATETIMESTAMP, statements.getStateTimestampColumn());
add(FIELD_ERRORDETAIL, statements.getErrorDetailColumn());
+ add(FIELD_PENDING, statements.getPendingColumn());
fieldMap.put(FIELD_CONTRACT_AGREEMENT, new ContractAgreementMapping(statements));
add(FIELD_TRACECONTEXT, statements.getTraceContextColumn());
diff --git a/extensions/control-plane/store/sql/transfer-process-store-sql/docs/schema.sql b/extensions/control-plane/store/sql/transfer-process-store-sql/docs/schema.sql
index 23beb113827..84784a62685 100644
--- a/extensions/control-plane/store/sql/transfer-process-store-sql/docs/schema.sql
+++ b/extensions/control-plane/store/sql/transfer-process-store-sql/docs/schema.sql
@@ -33,6 +33,7 @@ CREATE TABLE IF NOT EXISTS edc_transfer_process
deprovisioned_resources JSON,
private_properties JSON,
callback_addresses JSON,
+ pending BOOLEAN DEFAULT FALSE,
lease_id VARCHAR
CONSTRAINT transfer_process_lease_lease_id_fk
REFERENCES edc_lease
diff --git a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/SqlTransferProcessStore.java b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/SqlTransferProcessStore.java
index 0097c576e17..ad9a6c01621 100644
--- a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/SqlTransferProcessStore.java
+++ b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/SqlTransferProcessStore.java
@@ -242,9 +242,11 @@ private Stream executeQuery(Connection connection, QuerySpec qu
private void update(Connection conn, TransferProcess process, String existingDataRequestId) {
var updateStmt = statements.getUpdateTransferProcessTemplate();
- queryExecutor.execute(conn, updateStmt, process.getState(),
+ queryExecutor.execute(conn, updateStmt,
+ process.getState(),
process.getStateCount(),
process.getStateTimestamp(),
+ process.getUpdatedAt(),
toJson(process.getTraceContext()),
process.getErrorDetail(),
toJson(process.getResourceManifest()),
@@ -252,7 +254,7 @@ private void update(Connection conn, TransferProcess process, String existingDat
toJson(process.getContentDataAddress()),
toJson(process.getDeprovisionedResources()),
toJson(process.getCallbackAddresses()),
- process.getUpdatedAt(),
+ process.isPending(),
process.getId());
var newDr = process.getDataRequest();
@@ -307,7 +309,8 @@ private void insert(Connection conn, TransferProcess process) {
process.getType().toString(),
toJson(process.getDeprovisionedResources()),
toJson(process.getPrivateProperties()),
- toJson(process.getCallbackAddresses()));
+ toJson(process.getCallbackAddresses()),
+ process.isPending());
//insert DataRequest
var dr = process.getDataRequest();
@@ -350,6 +353,7 @@ private TransferProcess mapTransferProcess(ResultSet resultSet) throws SQLExcept
.callbackAddresses(fromJson(resultSet.getString(statements.getCallbackAddressesColumn()), new TypeReference<>() {
}))
.privateProperties(fromJson(resultSet.getString(statements.getPrivatePropertiesColumn()), getTypeRef()))
+ .pending(resultSet.getBoolean(statements.getPendingColumn()))
.build();
}
diff --git a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/BaseSqlDialectStatements.java b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/BaseSqlDialectStatements.java
index 71a0adf4e5d..e7324fbe916 100644
--- a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/BaseSqlDialectStatements.java
+++ b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/BaseSqlDialectStatements.java
@@ -16,9 +16,11 @@
import org.eclipse.edc.connector.store.sql.transferprocess.store.schema.postgres.TransferProcessMapping;
import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.sql.statement.SqlExecuteStatement;
import org.eclipse.edc.sql.translation.SqlQueryStatement;
import static java.lang.String.format;
+import static org.eclipse.edc.sql.statement.ColumnEntry.standardColumn;
/**
* Postgres-specific variants and implementations of the statements required for the TransferProcessStore
@@ -51,16 +53,24 @@ public String getFindLeaseByEntityTemplate() {
@Override
public String getInsertStatement() {
- return format("INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES (?, ?, ?, ?, ?, ?, ?%s, ?, ?%s, ?%s, ?%s, ?, ?%s, ?%s, ?%s);",
- // keys
- getTransferProcessTableName(), getIdColumn(), getStateColumn(), getStateCountColumn(), getStateTimestampColumn(),
- getCreatedAtColumn(), getUpdatedAtColumn(),
- getTraceContextColumn(), getErrorDetailColumn(), getResourceManifestColumn(),
- getProvisionedResourceSetColumn(), getContentDataAddressColumn(), getTypeColumn(), getDeprovisionedResourcesColumn(),
- getPrivatePropertiesColumn(), getCallbackAddressesColumn(),
- // values
- getFormatAsJsonOperator(), getFormatAsJsonOperator(), getFormatAsJsonOperator(), getFormatAsJsonOperator(),
- getFormatAsJsonOperator(), getFormatAsJsonOperator(), getFormatAsJsonOperator());
+ return SqlExecuteStatement.newInstance(getFormatAsJsonOperator())
+ .column(getIdColumn())
+ .column(getStateColumn())
+ .column(getStateCountColumn())
+ .column(getStateTimestampColumn())
+ .column(getCreatedAtColumn())
+ .column(getUpdatedAtColumn())
+ .jsonColumn(getTraceContextColumn())
+ .column(getErrorDetailColumn())
+ .jsonColumn(getResourceManifestColumn())
+ .jsonColumn(getProvisionedResourceSetColumn())
+ .jsonColumn(getContentDataAddressColumn())
+ .column(getTypeColumn())
+ .jsonColumn(getDeprovisionedResourcesColumn())
+ .jsonColumn(getPrivatePropertiesColumn())
+ .jsonColumn(getCallbackAddressesColumn())
+ .column(getPendingColumn())
+ .insertInto(getTransferProcessTableName());
}
@Override
@@ -70,12 +80,20 @@ public String getDeleteTransferProcessTemplate() {
@Override
public String getUpdateTransferProcessTemplate() {
- return format("UPDATE %s SET %s=?, %s=?, %s=?, %s=?%s, %s=?, %s=?%s, %s=?%s, %s=?%s, %s=?%s, %s=?%s, %s=? WHERE %s=?",
- getTransferProcessTableName(), getStateColumn(), getStateCountColumn(), getStateTimestampColumn(),
- getTraceContextColumn(), getFormatAsJsonOperator(), getErrorDetailColumn(),
- getResourceManifestColumn(), getFormatAsJsonOperator(), getProvisionedResourceSetColumn(), getFormatAsJsonOperator(),
- getContentDataAddressColumn(), getFormatAsJsonOperator(), getDeprovisionedResourcesColumn(), getFormatAsJsonOperator(),
- getCallbackAddressesColumn(), getFormatAsJsonOperator(), getUpdatedAtColumn(), getIdColumn());
+ return SqlExecuteStatement.newInstance(getFormatAsJsonOperator())
+ .column(getStateColumn())
+ .column(getStateCountColumn())
+ .column(getStateTimestampColumn())
+ .column(getUpdatedAtColumn())
+ .jsonColumn(getTraceContextColumn())
+ .column(getErrorDetailColumn())
+ .jsonColumn(getResourceManifestColumn())
+ .jsonColumn(getProvisionedResourceSetColumn())
+ .jsonColumn(getContentDataAddressColumn())
+ .jsonColumn(getDeprovisionedResourcesColumn())
+ .jsonColumn(getCallbackAddressesColumn())
+ .column(getPendingColumn())
+ .update(getTransferProcessTableName(), standardColumn(getIdColumn()));
}
@Override
@@ -104,4 +122,5 @@ public String getUpdateDataRequestTemplate() {
public SqlQueryStatement createQuery(QuerySpec querySpec) {
return new SqlQueryStatement(getSelectTemplate(), querySpec, new TransferProcessMapping(this));
}
+
}
diff --git a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/TransferProcessStoreStatements.java b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/TransferProcessStoreStatements.java
index 0a1c762860b..4167a03bc4d 100644
--- a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/TransferProcessStoreStatements.java
+++ b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/TransferProcessStoreStatements.java
@@ -142,6 +142,10 @@ default String getCallbackAddressesColumn() {
return "callback_addresses";
}
+ default String getPendingColumn() {
+ return "pending";
+ }
+
default String getFormatAsJsonOperator() {
return BaseSqlDialect.getJsonCastOperator();
}
diff --git a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/postgres/TransferProcessMapping.java b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/postgres/TransferProcessMapping.java
index 7836b46a6c6..480cf7c820f 100644
--- a/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/postgres/TransferProcessMapping.java
+++ b/extensions/control-plane/store/sql/transfer-process-store-sql/src/main/java/org/eclipse/edc/connector/store/sql/transferprocess/store/schema/postgres/TransferProcessMapping.java
@@ -40,6 +40,7 @@ public class TransferProcessMapping extends TranslationMapping {
private static final String FIELD_DEPROVISIONED_RESOURCES = "deprovisionedResources";
private static final String FIELD_PRIVATE_PROPERTIES = "privateProperties";
+ private static final String FIELD_PENDING = "pending";
public TransferProcessMapping(TransferProcessStoreStatements statements) {
@@ -57,5 +58,6 @@ public TransferProcessMapping(TransferProcessStoreStatements statements) {
add(FIELD_PROVISIONED_RESOURCE_SET, new ProvisionedResourceSetMapping());
// using the alias instead of the actual column name to avoid name clashes.
add(FIELD_DEPROVISIONED_RESOURCES, new JsonFieldMapping(PostgresDialectStatements.DEPROVISIONED_RESOURCES_ALIAS));
+ add(FIELD_PENDING, statements.getPendingColumn());
}
}
diff --git a/extensions/control-plane/store/sql/transfer-process-store-sql/src/test/java/org/eclipse/edc/connector/store/sql/transferprocess/PostgresTransferProcessStoreTest.java b/extensions/control-plane/store/sql/transfer-process-store-sql/src/test/java/org/eclipse/edc/connector/store/sql/transferprocess/PostgresTransferProcessStoreTest.java
index beb0717a6e1..b1890e8310e 100644
--- a/extensions/control-plane/store/sql/transfer-process-store-sql/src/test/java/org/eclipse/edc/connector/store/sql/transferprocess/PostgresTransferProcessStoreTest.java
+++ b/extensions/control-plane/store/sql/transfer-process-store-sql/src/test/java/org/eclipse/edc/connector/store/sql/transferprocess/PostgresTransferProcessStoreTest.java
@@ -87,8 +87,8 @@ void find_queryByDataRequest_propNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.dataRequest(da)
.build();
- store.updateOrCreate(tp);
- store.updateOrCreate(createTransferProcess("testprocess2"));
+ store.save(tp);
+ store.save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("dataRequest.notexist", "=", "somevalue")))
@@ -106,8 +106,8 @@ void find_queryByResourceManifest_propNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.resourceManifest(rm)
.build();
- store.updateOrCreate(tp);
- store.updateOrCreate(createTransferProcess("testprocess2"));
+ store.save(tp);
+ store.save(createTransferProcess("testprocess2"));
// throws exception when an explicit mapping exists
var query = QuerySpec.Builder.newInstance()
@@ -139,8 +139,8 @@ void find_queryByProvisionedResourceSet_propNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.provisionedResourceSet(prs)
.build();
- store.updateOrCreate(tp);
- store.updateOrCreate(createTransferProcess("testprocess2"));
+ store.save(tp);
+ store.save(createTransferProcess("testprocess2"));
// throws exception when an explicit mapping exists
var query = QuerySpec.Builder.newInstance()
@@ -160,7 +160,7 @@ void find_queryByProvisionedResourceSet_propNotExist() {
@Test
void find_queryByLease() {
- store.updateOrCreate(createTransferProcess("testprocess1"));
+ store.save(createTransferProcess("testprocess1"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("lease.leasedBy", "=", "foobar")))
@@ -177,13 +177,13 @@ void create_withoutDataRequest_throwsException() {
var t1 = TestFunctions.createTransferProcessBuilder("id1")
.dataRequest(null)
.build();
- assertThatIllegalArgumentException().isThrownBy(() -> getTransferProcessStore().updateOrCreate(t1));
+ assertThatIllegalArgumentException().isThrownBy(() -> getTransferProcessStore().save(t1));
}
@Override
@Test
protected void findAll_verifySorting_invalidProperty() {
- range(0, 10).forEach(i -> getTransferProcessStore().updateOrCreate(createTransferProcess("test-neg-" + i)));
+ range(0, 10).forEach(i -> getTransferProcessStore().save(createTransferProcess("test-neg-" + i)));
var query = QuerySpec.Builder.newInstance().sortField("notexist").sortOrder(SortOrder.DESC).build();
diff --git a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/PendingGuard.java b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/PendingGuard.java
new file mode 100644
index 00000000000..60e937f7701
--- /dev/null
+++ b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/PendingGuard.java
@@ -0,0 +1,28 @@
+/*
+ * 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.edc.spi.entity;
+
+import java.util.function.Predicate;
+
+/**
+ * Provides a way to control the flow of an entity in the state machine.
+ * If the function returns true, the entity will be marked as "pending" and it won't be picked up
+ * again by the state machine.
+ *
+ * @param the entity type
+ */
+@FunctionalInterface
+public interface PendingGuard> extends Predicate {
+}
diff --git a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/StatefulEntity.java b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/StatefulEntity.java
index 043cc737923..bd85b1e690c 100644
--- a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/StatefulEntity.java
+++ b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/StatefulEntity.java
@@ -35,6 +35,7 @@ public abstract class StatefulEntity> extends Mutabl
protected long stateTimestamp;
protected Map traceContext = new HashMap<>();
protected String errorDetail;
+ protected boolean pending = false;
protected StatefulEntity() {
}
@@ -60,10 +61,18 @@ public String getErrorDetail() {
return errorDetail;
}
+ public boolean isPending() {
+ return pending;
+ }
+
public void setErrorDetail(String errorDetail) {
this.errorDetail = errorDetail;
}
+ public void setPending(boolean pending) {
+ this.pending = pending;
+ }
+
/**
* Sets the state timestamp to the clock time.
*
@@ -100,6 +109,7 @@ protected > T copy(Builder builder) {
.traceContext(traceContext)
.errorDetail(errorDetail)
.clock(clock)
+ .pending(pending)
.build();
}
@@ -141,6 +151,11 @@ public B traceContext(Map traceContext) {
return self();
}
+ public B pending(boolean pending) {
+ entity.pending = pending;
+ return self();
+ }
+
protected T build() {
super.build();
if (entity.id == null) {
diff --git a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/persistence/StateEntityStore.java b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/persistence/StateEntityStore.java
index 356f13fd3b7..4f6db745907 100644
--- a/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/persistence/StateEntityStore.java
+++ b/spi/common/core-spi/src/main/java/org/eclipse/edc/spi/persistence/StateEntityStore.java
@@ -35,6 +35,10 @@ static Criterion hasState(int stateCode) {
return new Criterion("state", "=", stateCode);
}
+ static Criterion isNotPending() {
+ return new Criterion("pending", "=", false);
+ }
+
/**
* Returns a list of not leased entities that satisfy the filter criteria.
*
diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationConfirmed.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationConfirmed.java
deleted file mode 100644
index 90f3c15d722..00000000000
--- a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationConfirmed.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2022 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
- * Fraunhofer Institute for Software and Systems Engineering - expending Event classes
- *
- */
-
-package org.eclipse.edc.connector.contract.spi.event.contractnegotiation;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-
-/**
- * This event is raised when the ContractNegotiation has been confirmed.
- *
- * @deprecated please use {@link ContractNegotiationAgreed}
- */
-@Deprecated(since = "milestone9")
-@JsonDeserialize(builder = ContractNegotiationConfirmed.Builder.class)
-public class ContractNegotiationConfirmed extends ContractNegotiationEvent {
-
- private ContractNegotiationConfirmed() {
- }
-
- @Override
- public String name() {
- return "contract.negotiation.confirmed";
- }
-
- @JsonPOJOBuilder(withPrefix = "")
- public static class Builder extends ContractNegotiationEvent.Builder {
-
- @JsonCreator
- private Builder() {
- super(new ContractNegotiationConfirmed());
- }
-
- public static Builder newInstance() {
- return new Builder();
- }
-
- @Override
- public Builder self() {
- return this;
- }
- }
-}
diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationDeclined.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationDeclined.java
deleted file mode 100644
index 2aad562466f..00000000000
--- a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationDeclined.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2022 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
- * Fraunhofer Institute for Software and Systems Engineering - expending Event classes
- *
- */
-
-package org.eclipse.edc.connector.contract.spi.event.contractnegotiation;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-
-/**
- * This event is raised when the ContractNegotiation has been declined.
- *
- * @deprecated please use {@link ContractNegotiationTerminated}
- */
-@Deprecated(since = "milestone9")
-@JsonDeserialize(builder = ContractNegotiationDeclined.Builder.class)
-public class ContractNegotiationDeclined extends ContractNegotiationEvent {
-
- private ContractNegotiationDeclined() {
- }
-
- @Override
- public String name() {
- return "contract.negotiation.declined";
- }
-
- @JsonPOJOBuilder(withPrefix = "")
- public static class Builder extends ContractNegotiationEvent.Builder {
-
- private Builder() {
- super(new ContractNegotiationDeclined());
- }
-
- @JsonCreator
- public static Builder newInstance() {
- return new Builder();
- }
-
- @Override
- public Builder self() {
- return this;
- }
- }
-
-}
diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationFailed.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationFailed.java
deleted file mode 100644
index cb2d3c1dada..00000000000
--- a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationFailed.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2022 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
- * Fraunhofer Institute for Software and Systems Engineering - expending Event classes
- *
- */
-
-package org.eclipse.edc.connector.contract.spi.event.contractnegotiation;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-
-/**
- * This event is raised when the ContractNegotiation has failed.
- *
- * @deprecated please use {@link ContractNegotiationTerminated}
- */
-@Deprecated(since = "milestone9")
-@JsonDeserialize(builder = ContractNegotiationFailed.Builder.class)
-public class ContractNegotiationFailed extends ContractNegotiationEvent {
-
- private ContractNegotiationFailed() {
- }
-
- @Override
- public String name() {
- return "contract.negotiation.failed";
- }
-
- @JsonPOJOBuilder(withPrefix = "")
- public static class Builder extends ContractNegotiationEvent.Builder {
-
- private Builder() {
- super(new ContractNegotiationFailed());
- }
-
- @JsonCreator
- public static Builder newInstance() {
- return new Builder();
- }
-
- @Override
- public Builder self() {
- return this;
- }
- }
-
-}
diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/ContractNegotiationPendingGuard.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/ContractNegotiationPendingGuard.java
new file mode 100644
index 00000000000..520d3568c0c
--- /dev/null
+++ b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/ContractNegotiationPendingGuard.java
@@ -0,0 +1,28 @@
+/*
+ * 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.edc.connector.contract.spi.negotiation;
+
+import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
+import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint;
+import org.eclipse.edc.spi.entity.PendingGuard;
+
+/**
+ * Marker interface to define a service that permits to choose whether a ContractNegotiation will be waiting for an external
+ * interaction.
+ */
+@FunctionalInterface
+@ExtensionPoint
+public interface ContractNegotiationPendingGuard extends PendingGuard {
+}
diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/observe/ContractNegotiationListener.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/observe/ContractNegotiationListener.java
index 97f07b34474..1bc76a10375 100644
--- a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/observe/ContractNegotiationListener.java
+++ b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/observe/ContractNegotiationListener.java
@@ -72,28 +72,6 @@ default void terminated(ContractNegotiation negotiation) {
}
- /**
- * Called after a {@link ContractNegotiation} was declined.
- *
- * @param negotiation the contract negotiation that has been declined.
- * @deprecated please use {@link #terminated(ContractNegotiation)}
- */
- @Deprecated(since = "milestone9")
- default void declined(ContractNegotiation negotiation) {
- terminated(negotiation);
- }
-
- /**
- * Called after a {@link ContractNegotiation} failed.
- *
- * @param negotiation the contract negotiation that failed.
- * @deprecated please use {@link #terminated(ContractNegotiation)}
- */
- @Deprecated(since = "milestone9")
- default void failed(ContractNegotiation negotiation) {
- terminated(negotiation);
- }
-
/**
* Called after a {@link ContractNegotiation} was agreed by the provider.
*
diff --git a/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/event/ContractEventTest.java b/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/event/ContractEventTest.java
index 6ae6fb1a26d..0bf9d671f65 100644
--- a/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/event/ContractEventTest.java
+++ b/spi/control-plane/contract-spi/src/test/java/org/eclipse/edc/connector/contract/spi/event/ContractEventTest.java
@@ -18,10 +18,7 @@
import org.eclipse.edc.connector.contract.spi.event.contractdefinition.ContractDefinitionCreated;
import org.eclipse.edc.connector.contract.spi.event.contractdefinition.ContractDefinitionDeleted;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAccepted;
-import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationConfirmed;
-import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationDeclined;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationEvent;
-import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFailed;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationInitiated;
import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationOffered;
@@ -76,9 +73,6 @@ public Stream extends Arguments> provideArguments(ExtensionContext context) {
ContractDefinitionCreated.Builder.newInstance().contractDefinitionId("id").build(),
ContractDefinitionDeleted.Builder.newInstance().contractDefinitionId("id").build(),
baseBuilder(ContractNegotiationAccepted.Builder.newInstance()).build(),
- baseBuilder(ContractNegotiationConfirmed.Builder.newInstance()).build(),
- baseBuilder(ContractNegotiationDeclined.Builder.newInstance()).build(),
- baseBuilder(ContractNegotiationFailed.Builder.newInstance()).build(),
baseBuilder(ContractNegotiationInitiated.Builder.newInstance()).build(),
baseBuilder(ContractNegotiationOffered.Builder.newInstance()).build(),
baseBuilder(ContractNegotiationRequested.Builder.newInstance()).build(),
diff --git a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/ContractNegotiationStoreTestBase.java b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/ContractNegotiationStoreTestBase.java
index 0ca2f52c052..81046343e3e 100644
--- a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/ContractNegotiationStoreTestBase.java
+++ b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/ContractNegotiationStoreTestBase.java
@@ -35,8 +35,10 @@
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.eclipse.edc.connector.contract.spi.testfixtures.negotiation.store.TestFunctions.createCallbackAddress;
import static org.eclipse.edc.connector.contract.spi.testfixtures.negotiation.store.TestFunctions.createContract;
import static org.eclipse.edc.connector.contract.spi.testfixtures.negotiation.store.TestFunctions.createContractBuilder;
import static org.eclipse.edc.connector.contract.spi.testfixtures.negotiation.store.TestFunctions.createNegotiation;
@@ -54,14 +56,17 @@ public abstract class ContractNegotiationStoreTestBase {
@Test
@DisplayName("Verify that an entity is found by ID")
void find() {
- var id = "test-cn1";
- var negotiation = createNegotiation(id);
+ var id = UUID.randomUUID().toString();
+ var negotiation = createNegotiationBuilder(id)
+ .pending(true)
+ .build();
getContractNegotiationStore().save(negotiation);
- assertThat(getContractNegotiationStore().findById(id))
+ var result = getContractNegotiationStore().findById(id);
+
+ assertThat(result)
.usingRecursiveComparison()
.isEqualTo(negotiation);
-
}
@Test
@@ -272,21 +277,28 @@ void update_addsAgreement_shouldPersist() {
}
@Test
- @DisplayName("Should persist update the callbacks if changed")
- void update_changeCallbacks() {
- var negotiationId = "test-cn1";
- var negotiation = createNegotiation(negotiationId);
- getContractNegotiationStore().save(negotiation);
+ void update_changeFields() {
+ var negotiationId = UUID.randomUUID().toString();
+ var builder = createNegotiationBuilder(negotiationId)
+ .callbackAddresses(List.of(createCallbackAddress()))
+ .pending(false);
+ getContractNegotiationStore().save(builder.build());
- // one callback
- assertThat(Objects.requireNonNull(getContractNegotiationStore().findById(negotiationId)).getCallbackAddresses()).hasSize(1);
+ var inserted = getContractNegotiationStore().findById(negotiationId);
+ assertThat(inserted).isNotNull().satisfies(i -> {
+ assertThat(i.getCallbackAddresses()).hasSize(1);
+ assertThat(i.isPending()).isFalse();
+ });
- // remove callbacks
- var updatedNegotiation = createNegotiationBuilder(negotiationId).callbackAddresses(List.of()).build();
+ builder.callbackAddresses(emptyList()).pending(true);
- getContractNegotiationStore().save(updatedNegotiation); //should perform an update + insert
+ getContractNegotiationStore().save(builder.build());
- assertThat(Objects.requireNonNull(getContractNegotiationStore().findById(negotiationId)).getCallbackAddresses()).isEmpty();
+ var updated = getContractNegotiationStore().findById(negotiationId);
+ assertThat(updated).isNotNull().satisfies(u -> {
+ assertThat(u.getCallbackAddresses()).isEmpty();
+ assertThat(u.isPending()).isTrue();
+ });
}
@Test
diff --git a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/TestFunctions.java b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/TestFunctions.java
index 1bf11e76caf..1e2aef2bc7f 100644
--- a/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/TestFunctions.java
+++ b/spi/control-plane/contract-spi/src/testFixtures/java/org/eclipse/edc/connector/contract/spi/testfixtures/negotiation/store/TestFunctions.java
@@ -72,13 +72,17 @@ public static ContractNegotiation.Builder createNegotiationBuilder(String id) {
.state(ContractNegotiationStates.REQUESTED.code())
.counterPartyAddress("consumer")
.counterPartyId("consumerId")
- .callbackAddresses(List.of(CallbackAddress.Builder.newInstance()
- .uri("local://test")
- .events(Set.of("contract.negotiation.initiated"))
- .build()))
+ .callbackAddresses(List.of(createCallbackAddress()))
.protocol("protocol");
}
+ public static CallbackAddress createCallbackAddress() {
+ return CallbackAddress.Builder.newInstance()
+ .uri("local://test")
+ .events(Set.of("contract.negotiation.initiated"))
+ .build();
+ }
+
public static Policy createPolicy() {
return Policy.Builder.newInstance()
.permission(Permission.Builder.newInstance()
diff --git a/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/TransferProcessPendingGuard.java b/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/TransferProcessPendingGuard.java
new file mode 100644
index 00000000000..671a3fc09c7
--- /dev/null
+++ b/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/TransferProcessPendingGuard.java
@@ -0,0 +1,28 @@
+/*
+ * 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.edc.connector.transfer.spi;
+
+import org.eclipse.edc.connector.transfer.spi.types.TransferProcess;
+import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint;
+import org.eclipse.edc.spi.entity.PendingGuard;
+
+/**
+ * Marker interface to define a service that permits to choose whether a TransferProcess will be waiting for an external
+ * interaction.
+ */
+@FunctionalInterface
+@ExtensionPoint
+public interface TransferProcessPendingGuard extends PendingGuard {
+}
diff --git a/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessCancelled.java b/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessCancelled.java
deleted file mode 100644
index c3d387f5895..00000000000
--- a/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessCancelled.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2022 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made available under the
- * terms of the Apache License, Version 2.0 which is available at
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Contributors:
- * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
- *
- */
-
-package org.eclipse.edc.connector.transfer.spi.event;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-
-/**
- * This event is raised when the TransferProcess has been cancelled.
- *
- * @deprecated this event is not thrown by anyone, please use {@link TransferProcessTerminated} instead
- */
-@Deprecated(since = "milestone9")
-@JsonDeserialize(builder = TransferProcessCancelled.Builder.class)
-public class TransferProcessCancelled extends TransferProcessEvent {
-
- private TransferProcessCancelled() {
- }
-
- @Override
- public String name() {
- return "transfer.process.cancelled";
- }
-
-
- @JsonPOJOBuilder(withPrefix = "")
- public static class Builder extends TransferProcessEvent.Builder {
-
- private Builder() {
- super(new TransferProcessCancelled());
- }
-
- @JsonCreator
- public static Builder newInstance() {
- return new Builder();
- }
-
- @Override
- public Builder self() {
- return this;
- }
- }
-}
diff --git a/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessEnded.java b/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessEnded.java
deleted file mode 100644
index bb2c11d37dc..00000000000
--- a/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessEnded.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2022 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made available under the
- * terms of the Apache License, Version 2.0 which is available at
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Contributors:
- * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
- *
- */
-
-package org.eclipse.edc.connector.transfer.spi.event;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-
-/**
- * This event is raised when the TransferProcess has been ended.
- *
- * @deprecated this event is not thrown by anyone, please use {@link TransferProcessTerminated} instead
- */
-@Deprecated(since = "milestone9")
-@JsonDeserialize(builder = TransferProcessEvent.Builder.class)
-public class TransferProcessEnded extends TransferProcessEvent {
-
- private TransferProcessEnded() {
- }
-
- @Override
- public String name() {
- return "transfer.process.ended";
- }
-
- @JsonPOJOBuilder(withPrefix = "")
- public static class Builder extends TransferProcessEvent.Builder {
-
- private Builder() {
- super(new TransferProcessEnded());
- }
-
- @JsonCreator
- public static Builder newInstance() {
- return new Builder();
- }
-
- @Override
- public Builder self() {
- return this;
- }
- }
-
-}
diff --git a/spi/control-plane/transfer-spi/src/testFixtures/java/org/eclipse/edc/connector/transfer/spi/testfixtures/store/TransferProcessStoreTestBase.java b/spi/control-plane/transfer-spi/src/testFixtures/java/org/eclipse/edc/connector/transfer/spi/testfixtures/store/TransferProcessStoreTestBase.java
index e4c3fe216cd..04ad199eda4 100644
--- a/spi/control-plane/transfer-spi/src/testFixtures/java/org/eclipse/edc/connector/transfer/spi/testfixtures/store/TransferProcessStoreTestBase.java
+++ b/spi/control-plane/transfer-spi/src/testFixtures/java/org/eclipse/edc/connector/transfer/spi/testfixtures/store/TransferProcessStoreTestBase.java
@@ -86,8 +86,9 @@ void find_queryByState() {
void create() {
var transferProcess = createTransferProcessBuilder("test-id")
.dataRequest(createDataRequestBuilder().id("data-request-id").build())
+ .pending(true)
.privateProperties(Map.of("key", "value")).build();
- getTransferProcessStore().updateOrCreate(transferProcess);
+ getTransferProcessStore().save(transferProcess);
var retrieved = getTransferProcessStore().findById("test-id");
@@ -101,7 +102,7 @@ void create_verifyCallbacks() {
var callbacks = List.of(CallbackAddress.Builder.newInstance().uri("test").events(Set.of("event")).build());
var t = createTransferProcessBuilder("test-id").privateProperties(Map.of("key", "value")).callbackAddresses(callbacks).build();
- getTransferProcessStore().updateOrCreate(t);
+ getTransferProcessStore().save(t);
var all = getTransferProcessStore().findAll(QuerySpec.none()).collect(Collectors.toList());
assertThat(all).containsExactly(t);
@@ -112,10 +113,10 @@ void create_verifyCallbacks() {
@Test
void create_withSameIdExists_shouldReplace() {
var t = createTransferProcess("id1", INITIAL);
- getTransferProcessStore().updateOrCreate(t);
+ getTransferProcessStore().save(t);
var t2 = createTransferProcess("id1", PROVISIONING);
- getTransferProcessStore().updateOrCreate(t2);
+ getTransferProcessStore().save(t2);
assertThat(getTransferProcessStore().findAll(QuerySpec.none())).hasSize(1).containsExactly(t2);
}
@@ -125,7 +126,7 @@ void nextNotLeased() {
var state = STARTED;
var all = range(0, 10)
.mapToObj(i -> createTransferProcess("id" + i, state))
- .peek(getTransferProcessStore()::updateOrCreate)
+ .peek(getTransferProcessStore()::save)
.toList();
assertThat(getTransferProcessStore().nextNotLeased(5, hasState(state.code())))
@@ -140,7 +141,7 @@ void nextNotLeased_shouldOnlyReturnFreeItems() {
var state = STARTED;
var all = range(0, 10)
.mapToObj(i -> createTransferProcess("id" + i, state))
- .peek(getTransferProcessStore()::updateOrCreate)
+ .peek(getTransferProcessStore()::save)
.collect(Collectors.toList());
// lease a few
@@ -158,7 +159,7 @@ void nextNotLeased_noFreeItem_shouldReturnEmpty() {
var state = STARTED;
range(0, 3)
.mapToObj(i -> createTransferProcess("id" + i, state))
- .forEach(getTransferProcessStore()::updateOrCreate);
+ .forEach(getTransferProcessStore()::save);
// first time works
assertThat(getTransferProcessStore().nextNotLeased(10, hasState(state.code()))).hasSize(3);
@@ -170,7 +171,7 @@ void nextNotLeased_noFreeItem_shouldReturnEmpty() {
void nextNotLeased_noneInDesiredState() {
range(0, 3)
.mapToObj(i -> createTransferProcess("id" + i, STARTED))
- .forEach(getTransferProcessStore()::updateOrCreate);
+ .forEach(getTransferProcessStore()::save);
var nextNotLeased = getTransferProcessStore().nextNotLeased(10, hasState(TERMINATED.code()));
@@ -182,7 +183,7 @@ void nextNotLeased_batchSizeLimits() {
var state = STARTED;
range(0, 10)
.mapToObj(i -> createTransferProcess("id" + i, state))
- .forEach(getTransferProcessStore()::updateOrCreate);
+ .forEach(getTransferProcessStore()::save);
// first time works
var result = getTransferProcessStore().nextNotLeased(3, hasState(state.code()));
@@ -195,7 +196,7 @@ void nextNotLeased_verifyTemporalOrdering() {
range(0, 10)
.mapToObj(i -> createTransferProcess(String.valueOf(i), state))
.peek(this::delayByTenMillis)
- .forEach(getTransferProcessStore()::updateOrCreate);
+ .forEach(getTransferProcessStore()::save);
assertThat(getTransferProcessStore().nextNotLeased(20, hasState(state.code())))
.extracting(TransferProcess::getId)
@@ -207,14 +208,14 @@ void nextNotLeased_verifyTemporalOrdering() {
void nextNotLeased_verifyMostRecentlyUpdatedIsLast() throws InterruptedException {
var all = range(0, 10)
.mapToObj(i -> createTransferProcess("id" + i, STARTED))
- .peek(getTransferProcessStore()::updateOrCreate)
+ .peek(getTransferProcessStore()::save)
.toList();
Thread.sleep(100);
var fourth = all.get(3);
fourth.updateStateTimestamp();
- getTransferProcessStore().updateOrCreate(fourth);
+ getTransferProcessStore().save(fourth);
var next = getTransferProcessStore().nextNotLeased(20, hasState(STARTED.code()));
assertThat(next.indexOf(fourth)).isEqualTo(9);
@@ -224,7 +225,7 @@ void nextNotLeased_verifyMostRecentlyUpdatedIsLast() throws InterruptedException
@DisplayName("Verifies that calling nextNotLeased locks the TP for any subsequent calls")
void nextNotLeased_locksEntity() {
var t = createTransferProcess("id1", INITIAL);
- getTransferProcessStore().updateOrCreate(t);
+ getTransferProcessStore().save(t);
getTransferProcessStore().nextNotLeased(100, hasState(INITIAL.code()));
@@ -234,7 +235,7 @@ void nextNotLeased_locksEntity() {
@Test
void nextNotLeased_expiredLease() {
var t = createTransferProcess("id1", INITIAL);
- getTransferProcessStore().updateOrCreate(t);
+ getTransferProcessStore().save(t);
leaseEntity(t.getId(), CONNECTOR_NAME, Duration.ofMillis(100));
@@ -246,7 +247,7 @@ void nextNotLeased_expiredLease() {
@Test
void nextNotLeased_shouldLeaseEntityUntilUpdate() {
var initialTransferProcess = initialTransferProcess();
- getTransferProcessStore().updateOrCreate(initialTransferProcess);
+ getTransferProcessStore().save(initialTransferProcess);
var firstQueryResult = getTransferProcessStore().nextNotLeased(1, hasState(INITIAL.code()));
assertThat(firstQueryResult).hasSize(1);
@@ -255,7 +256,7 @@ void nextNotLeased_shouldLeaseEntityUntilUpdate() {
assertThat(secondQueryResult).hasSize(0);
var retrieved = firstQueryResult.get(0);
- getTransferProcessStore().updateOrCreate(retrieved);
+ getTransferProcessStore().save(retrieved);
var thirdQueryResult = getTransferProcessStore().nextNotLeased(1, hasState(INITIAL.code()));
assertThat(thirdQueryResult).hasSize(1);
@@ -265,14 +266,14 @@ void nextNotLeased_shouldLeaseEntityUntilUpdate() {
void nextNotLeased_avoidsStarvation() throws InterruptedException {
for (int i = 0; i < 10; i++) {
var process = createTransferProcess("test-process-" + i);
- getTransferProcessStore().updateOrCreate(process);
+ getTransferProcessStore().save(process);
}
var list1 = getTransferProcessStore().nextNotLeased(5, hasState(INITIAL.code()));
Thread.sleep(50); //simulate a short delay to generate different timestamps
list1.forEach(tp -> {
tp.updateStateTimestamp();
- getTransferProcessStore().updateOrCreate(tp);
+ getTransferProcessStore().save(tp);
});
var list2 = getTransferProcessStore().nextNotLeased(5, hasState(INITIAL.code()));
assertThat(list1).isNotEqualTo(list2).doesNotContainAnyElementsOf(list2);
@@ -281,7 +282,7 @@ void nextNotLeased_avoidsStarvation() throws InterruptedException {
@Test
void findById() {
var t = createTransferProcess("id1");
- getTransferProcessStore().updateOrCreate(t);
+ getTransferProcessStore().save(t);
var result = getTransferProcessStore().findById("id1");
@@ -299,7 +300,7 @@ void findById_notExist() {
void findForCorrelationId() {
var dataRequest = createDataRequestBuilder().id("correlationId").build();
var transferProcess = createTransferProcessBuilder("id1").dataRequest(dataRequest).build();
- getTransferProcessStore().updateOrCreate(transferProcess);
+ getTransferProcessStore().save(transferProcess);
var res = getTransferProcessStore().findForCorrelationId("correlationId");
@@ -314,10 +315,11 @@ void findForCorrelationId_notExist() {
@Test
void update_exists_shouldUpdate() {
var t1 = createTransferProcess("id1", STARTED);
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
t1.transitionCompleted(); //modify
- getTransferProcessStore().updateOrCreate(t1);
+ t1.setPending(true);
+ getTransferProcessStore().save(t1);
assertThat(getTransferProcessStore().findAll(QuerySpec.none()))
.hasSize(1)
@@ -330,7 +332,7 @@ void update_notExist_shouldCreate() {
var t1 = createTransferProcess("id1", STARTED);
t1.transitionCompleted(); //modify
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
var result = getTransferProcessStore().findAll(QuerySpec.none()).collect(Collectors.toList());
assertThat(result)
@@ -343,12 +345,12 @@ void update_notExist_shouldCreate() {
@DisplayName("Verify that the lease on a TP is cleared by an update")
void update_shouldBreakLease() {
var t1 = createTransferProcess("id1");
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
// acquire lease
leaseEntity(t1.getId(), CONNECTOR_NAME);
t1.transitionProvisioning(ResourceManifest.Builder.newInstance().build()); //modify
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
// lease should be broken
var notLeased = getTransferProcessStore().nextNotLeased(10, hasState(PROVISIONING.code()));
@@ -360,19 +362,19 @@ void update_shouldBreakLease() {
void update_leasedByOther_shouldThrowException() {
var tpId = "id1";
var t1 = createTransferProcess(tpId);
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
leaseEntity(tpId, "someone");
t1.transitionProvisioning(ResourceManifest.Builder.newInstance().build()); //modify
// leased by someone else -> throw exception
- assertThatThrownBy(() -> getTransferProcessStore().updateOrCreate(t1)).isInstanceOf(IllegalStateException.class);
+ assertThatThrownBy(() -> getTransferProcessStore().save(t1)).isInstanceOf(IllegalStateException.class);
}
@Test
void delete() {
var t1 = createTransferProcess("id1");
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
getTransferProcessStore().delete("id1");
assertThat(getTransferProcessStore().findAll(QuerySpec.none())).isEmpty();
@@ -381,7 +383,7 @@ void delete() {
@Test
void delete_isLeasedBySelf_shouldThrowException() {
var t1 = createTransferProcess("id1");
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
leaseEntity(t1.getId(), CONNECTOR_NAME);
@@ -391,7 +393,7 @@ void delete_isLeasedBySelf_shouldThrowException() {
@Test
void delete_isLeasedByOther_shouldThrowException() {
var t1 = createTransferProcess("id1");
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
leaseEntity(t1.getId(), "someone-else");
@@ -408,7 +410,7 @@ void delete_notExist() {
void findAll_noQuerySpec() {
var all = range(0, 10)
.mapToObj(i -> createTransferProcess("id" + i))
- .peek(getTransferProcessStore()::updateOrCreate)
+ .peek(getTransferProcessStore()::save)
.collect(Collectors.toList());
assertThat(getTransferProcessStore().findAll(QuerySpec.none())).containsExactlyInAnyOrderElementsOf(all);
@@ -418,7 +420,7 @@ void findAll_noQuerySpec() {
void findAll_verifyPaging() {
range(0, 10)
.mapToObj(i -> createTransferProcess(String.valueOf(i)))
- .forEach(getTransferProcessStore()::updateOrCreate);
+ .forEach(getTransferProcessStore()::save);
var qs = QuerySpec.Builder.newInstance().limit(5).offset(3).build();
assertThat(getTransferProcessStore().findAll(qs)).hasSize(5)
@@ -432,7 +434,7 @@ void findAll_verifyPaging_pageSizeLargerThanCollection() {
range(0, 10)
.mapToObj(i -> createTransferProcess(String.valueOf(i)))
- .forEach(getTransferProcessStore()::updateOrCreate);
+ .forEach(getTransferProcessStore()::save);
var qs = QuerySpec.Builder.newInstance().limit(20).offset(3).build();
assertThat(getTransferProcessStore().findAll(qs))
@@ -447,7 +449,7 @@ void findAll_verifyPaging_pageSizeOutsideCollection() {
range(0, 10)
.mapToObj(i -> createTransferProcess(String.valueOf(i)))
- .forEach(getTransferProcessStore()::updateOrCreate);
+ .forEach(getTransferProcessStore()::save);
var qs = QuerySpec.Builder.newInstance().limit(10).offset(12).build();
assertThat(getTransferProcessStore().findAll(qs)).isEmpty();
@@ -458,7 +460,7 @@ void findAll_verifyPaging_pageSizeOutsideCollection() {
void update_dataRequestWithNewId_replacesOld() {
var bldr = createTransferProcessBuilder("id1").state(STARTED.code());
var t1 = bldr.build();
- getTransferProcessStore().updateOrCreate(t1);
+ getTransferProcessStore().save(t1);
var t2 = bldr
.dataRequest(TestFunctions.createDataRequestBuilder()
@@ -469,7 +471,7 @@ void update_dataRequestWithNewId_replacesOld() {
.connectorId("new-connector")
.build())
.build();
- getTransferProcessStore().updateOrCreate(t2);
+ getTransferProcessStore().save(t2);
var all = getTransferProcessStore().findAll(QuerySpec.none()).collect(Collectors.toList());
assertThat(all)
@@ -492,8 +494,8 @@ void find_queryByDataAddressProperty() {
var tp = createTransferProcessBuilder("testprocess1")
.contentDataAddress(da)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("contentDataAddress.properties.key", "=", "value")))
@@ -513,8 +515,8 @@ void find_queryByDataAddress_propNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.contentDataAddress(da)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("contentDataAddress.properties.notexist", "=", "value")))
@@ -532,8 +534,8 @@ void find_queryByDataAddress_invalidKey_valueNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.contentDataAddress(da)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("contentDataAddress.properties.key", "=", "notexist")))
@@ -548,8 +550,8 @@ void find_queryByDataRequestProperty_processId() {
var tp = createTransferProcessBuilder("testprocess1")
.dataRequest(da)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("dataRequest.processId", "=", "testprocess1")))
@@ -566,8 +568,8 @@ void find_queryByDataRequestProperty_id() {
var tp = createTransferProcessBuilder("testprocess1")
.dataRequest(da)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("dataRequest.id", "=", da.getId())))
@@ -585,8 +587,8 @@ void find_queryByDataRequestProperty_protocol() {
var tp = createTransferProcessBuilder("testprocess1")
.dataRequest(da)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("dataRequest.protocol", "like", "%/protocol")))
@@ -603,8 +605,8 @@ void find_queryByDataRequest_valueNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.dataRequest(da)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("dataRequest.id", "=", "notexist")))
@@ -621,8 +623,8 @@ void find_queryByResourceManifestProperty() {
var tp = createTransferProcessBuilder("testprocess1")
.resourceManifest(rm)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("resourceManifest.definitions.id", "=", "rd-id")))
@@ -640,8 +642,8 @@ void find_queryByResourceManifest_valueNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.resourceManifest(rm)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
// throws exception when an explicit mapping exists
var query = QuerySpec.Builder.newInstance()
@@ -664,8 +666,8 @@ void find_queryByProvisionedResourceSetProperty() {
var tp = createTransferProcessBuilder("testprocess1")
.provisionedResourceSet(prs)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
var query = QuerySpec.Builder.newInstance()
.filter(List.of(new Criterion("provisionedResourceSet.resources.transferProcessId", "=", "testprocess1")))
@@ -689,8 +691,8 @@ void find_queryByProvisionedResourceSet_valueNotExist() {
var tp = createTransferProcessBuilder("testprocess1")
.provisionedResourceSet(prs)
.build();
- getTransferProcessStore().updateOrCreate(tp);
- getTransferProcessStore().updateOrCreate(createTransferProcess("testprocess2"));
+ getTransferProcessStore().save(tp);
+ getTransferProcessStore().save(createTransferProcess("testprocess2"));
// returns empty when the invalid value is embedded in JSON
@@ -724,8 +726,8 @@ void find_queryByDeprovisionedResourcesProperty() {
.deprovisionedResources(List.of(dp3))
.build();
- getTransferProcessStore().updateOrCreate(process1);
- getTransferProcessStore().updateOrCreate(process2);
+ getTransferProcessStore().save(process1);
+ getTransferProcessStore().save(process2);
var query = QuerySpec.Builder.newInstance()
.filter(Criterion.criterion("deprovisionedResources.inProcess", "=", "true"))
@@ -761,8 +763,8 @@ void find_queryByDeprovisionedResourcesProperty_multipleCriteria() {
.deprovisionedResources(List.of(dp3))
.build();
- getTransferProcessStore().updateOrCreate(process1);
- getTransferProcessStore().updateOrCreate(process2);
+ getTransferProcessStore().save(process1);
+ getTransferProcessStore().save(process2);
var query = QuerySpec.Builder.newInstance()
.filter(List.of(
@@ -801,8 +803,8 @@ void find_queryByDeprovisionedResourcesProperty_multipleResults() {
.deprovisionedResources(List.of(dp3))
.build();
- getTransferProcessStore().updateOrCreate(process1);
- getTransferProcessStore().updateOrCreate(process2);
+ getTransferProcessStore().save(process1);
+ getTransferProcessStore().save(process2);
var query = QuerySpec.Builder.newInstance()
.filter(Criterion.criterion("deprovisionedResources.inProcess", "=", "false"))
@@ -830,7 +832,7 @@ void find_queryByDeprovisionedResources_propNotExist() {
var process1 = createTransferProcessBuilder("test-pid1")
.deprovisionedResources(List.of(dp1, dp2))
.build();
- getTransferProcessStore().updateOrCreate(process1);
+ getTransferProcessStore().save(process1);
var query = QuerySpec.Builder.newInstance()
.filter(Criterion.criterion("deprovisionedResources.foobar", "=", "barbaz"))
@@ -856,7 +858,7 @@ void find_queryByDeprovisionedResources_valueNotExist() {
var process1 = createTransferProcessBuilder("test-pid1")
.deprovisionedResources(List.of(dp1, dp2))
.build();
- getTransferProcessStore().updateOrCreate(process1);
+ getTransferProcessStore().save(process1);
var query = QuerySpec.Builder.newInstance()
.filter(Criterion.criterion("deprovisionedResources.errorMessage", "=", "notexist"))
@@ -871,7 +873,7 @@ void find_queryByDeprovisionedResources_valueNotExist() {
void verifyCreateUpdateDelete() {
var id = UUID.randomUUID().toString();
var transferProcess = createTransferProcess(id, createDataRequestBuilder().id("correlationId").build());
- getTransferProcessStore().updateOrCreate(transferProcess);
+ getTransferProcessStore().save(transferProcess);
var found = getTransferProcessStore().findById(id);
@@ -884,7 +886,7 @@ void verifyCreateUpdateDelete() {
transferProcess.transitionProvisioning(ResourceManifest.Builder.newInstance().build());
- getTransferProcessStore().updateOrCreate(transferProcess);
+ getTransferProcessStore().save(transferProcess);
found = getTransferProcessStore().findById(id);
assertNotNull(found);
@@ -897,7 +899,7 @@ void verifyCreateUpdateDelete() {
@Test
void findAll_verifyFiltering() {
- range(0, 10).forEach(i -> getTransferProcessStore().updateOrCreate(createTransferProcess("test-neg-" + i)));
+ range(0, 10).forEach(i -> getTransferProcessStore().save(createTransferProcess("test-neg-" + i)));
var querySpec = QuerySpec.Builder.newInstance().filter(Criterion.criterion("id", "=", "test-neg-3")).build();
var result = getTransferProcessStore().findAll(querySpec);
@@ -907,7 +909,7 @@ void findAll_verifyFiltering() {
@Test
void findAll_verifyFiltering_invalidFilterExpression() {
- range(0, 10).forEach(i -> getTransferProcessStore().updateOrCreate(createTransferProcess("test-neg-" + i)));
+ range(0, 10).forEach(i -> getTransferProcessStore().save(createTransferProcess("test-neg-" + i)));
var querySpec = QuerySpec.Builder.newInstance().filter(Criterion.criterion("something", "foobar", "other")).build();
assertThatThrownBy(() -> getTransferProcessStore().findAll(querySpec)).isInstanceOfAny(IllegalArgumentException.class);
@@ -915,16 +917,25 @@ void findAll_verifyFiltering_invalidFilterExpression() {
@Test
void findAll_verifySorting() {
- range(0, 10).forEach(i -> getTransferProcessStore().updateOrCreate(createTransferProcess("test-neg-" + i)));
+ range(0, 10).forEach(i -> getTransferProcessStore().save(createTransferProcess("test-neg-" + i)));
assertThat(getTransferProcessStore().findAll(QuerySpec.Builder.newInstance().sortField("id").sortOrder(SortOrder.ASC).build())).hasSize(10).isSortedAccordingTo(Comparator.comparing(TransferProcess::getId));
assertThat(getTransferProcessStore().findAll(QuerySpec.Builder.newInstance().sortField("id").sortOrder(SortOrder.DESC).build())).hasSize(10).isSortedAccordingTo((c1, c2) -> c2.getId().compareTo(c1.getId()));
}
+ @Test
+ protected void findAll_verifySorting_invalidProperty() {
+ range(0, 10).forEach(i -> getTransferProcessStore().save(createTransferProcess("test-neg-" + i)));
+
+ var query = QuerySpec.Builder.newInstance().sortField("notexist").sortOrder(SortOrder.DESC).build();
+
+ assertThat(getTransferProcessStore().findAll(query)).isEmpty();
+ }
+
@Test
void findByIdAndLease_shouldReturnTheEntityAndLeaseIt() {
var id = UUID.randomUUID().toString();
- getTransferProcessStore().updateOrCreate(createTransferProcess(id));
+ getTransferProcessStore().save(createTransferProcess(id));
var result = getTransferProcessStore().findByIdAndLease(id);
@@ -942,7 +953,7 @@ void findByIdAndLease_shouldReturnNotFound_whenEntityDoesNotExist() {
@Test
void findByIdAndLease_shouldReturnAlreadyLeased_whenEntityIsAlreadyLeased() {
var id = UUID.randomUUID().toString();
- getTransferProcessStore().updateOrCreate(createTransferProcess(id));
+ getTransferProcessStore().save(createTransferProcess(id));
leaseEntity(id, "other owner");
var result = getTransferProcessStore().findByIdAndLease(id);
@@ -983,15 +994,6 @@ void findByCorrelationIdAndLease_shouldReturnAlreadyLeased_whenEntityIsAlreadyLe
assertThat(result).isFailed().extracting(StoreFailure::getReason).isEqualTo(ALREADY_LEASED);
}
- @Test
- protected void findAll_verifySorting_invalidProperty() {
- range(0, 10).forEach(i -> getTransferProcessStore().updateOrCreate(createTransferProcess("test-neg-" + i)));
-
- var query = QuerySpec.Builder.newInstance().sortField("notexist").sortOrder(SortOrder.DESC).build();
-
- assertThat(getTransferProcessStore().findAll(query).collect(Collectors.toList())).isEmpty();
- }
-
protected abstract boolean supportsCollectionQuery();
protected abstract boolean supportsLikeOperator();
diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java
index 27839807c03..7e7685d5aa6 100644
--- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java
+++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java
@@ -56,8 +56,8 @@ public class TransferProcessApiEndToEndTest extends BaseManagementApiEndToEndTes
@Test
void getAll() {
- getStore().updateOrCreate(createTransferProcess("tp1"));
- getStore().updateOrCreate(createTransferProcess("tp2"));
+ getStore().save(createTransferProcess("tp1"));
+ getStore().save(createTransferProcess("tp2"));
baseRequest()
.contentType(JSON)
@@ -71,8 +71,8 @@ void getAll() {
@Test
void getById() {
- getStore().updateOrCreate(createTransferProcess("tp1"));
- getStore().updateOrCreate(createTransferProcess("tp2"));
+ getStore().save(createTransferProcess("tp1"));
+ getStore().save(createTransferProcess("tp2"));
baseRequest()
.get("/v2/transferprocesses/tp2")
@@ -84,7 +84,7 @@ void getById() {
@Test
void getState() {
- getStore().updateOrCreate(createTransferProcessBuilder("tp2").state(COMPLETED.code()).build());
+ getStore().save(createTransferProcessBuilder("tp2").state(COMPLETED.code()).build());
baseRequest()
.get("/v2/transferprocesses/tp2/state")
@@ -131,7 +131,7 @@ void create() {
@Test
void deprovision() {
var id = UUID.randomUUID().toString();
- getStore().updateOrCreate(createTransferProcessBuilder(id).state(COMPLETED.code()).build());
+ getStore().save(createTransferProcessBuilder(id).state(COMPLETED.code()).build());
baseRequest()
.contentType(JSON)
@@ -143,7 +143,7 @@ void deprovision() {
@Test
void terminate() {
var id = UUID.randomUUID().toString();
- getStore().updateOrCreate(createTransferProcessBuilder(id).state(REQUESTED.code()).build());
+ getStore().save(createTransferProcessBuilder(id).state(REQUESTED.code()).build());
var requestBody = createObjectBuilder()
.add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE))
.add("reason", "any")