From 102b20ff1e38f7b1413e1fc986658833cc128f35 Mon Sep 17 00:00:00 2001 From: ndr_brt Date: Thu, 20 Jul 2023 09:26:37 +0200 Subject: [PATCH 1/2] feat: add manual interactions to ContractNegotiation and TransferProcess --- .../{StateProcessor.java => Processor.java} | 2 +- .../edc/statemachine/ProcessorImpl.java | 94 ++++++++++ .../edc/statemachine/StateMachineManager.java | 34 ++-- .../edc/statemachine/StateProcessorImpl.java | 4 +- .../edc/statemachine/ProcessorImplTest.java | 91 ++++++++++ .../statemachine/StateMachineManagerTest.java | 10 +- .../edc/statemachine/retry/TestEntity.java | 4 +- .../contract/ContractCoreExtension.java | 41 +++-- ...ctNegotiationDefaultServicesExtension.java | 70 ++++++++ .../ContractNegotiationEventListener.java | 14 -- .../AbstractContractNegotiationManager.java | 29 +++ ...onsumerContractNegotiationManagerImpl.java | 15 +- ...roviderContractNegotiationManagerImpl.java | 12 +- ...rg.eclipse.edc.spi.system.ServiceExtension | 1 + .../contract/ContractCoreExtensionTest.java | 22 +-- .../ContractNegotiationEventListenerTest.java | 43 ----- ...merContractNegotiationManagerImplTest.java | 44 +++-- ...derContractNegotiationManagerImplTest.java | 42 +++-- .../transfer/TransferCoreExtension.java | 64 ++++--- ...ansferProcessDefaultServicesExtension.java | 78 ++++++++ .../process/TransferProcessManagerImpl.java | 27 ++- ...rg.eclipse.edc.spi.system.ServiceExtension | 1 + .../TransferProcessManagerImplTest.java | 38 ++-- docs/developer/state-machine.md | 115 ++++++++---- .../edc/sql/statement/ColumnEntry.java | 36 ++++ .../sql/statement/SqlExecuteStatement.java | 65 +++++++ ...nsferProcessHttpClientIntegrationTest.java | 6 +- .../docs/schema.sql | 17 +- .../store/SqlContractNegotiationStore.java | 12 +- .../schema/BaseSqlDialectStatements.java | 43 +++-- .../schema/ContractNegotiationStatements.java | 4 + .../postgres/ContractNegotiationMapping.java | 3 +- .../docs/schema.sql | 1 + .../store/SqlTransferProcessStore.java | 10 +- .../schema/BaseSqlDialectStatements.java | 51 ++++-- .../TransferProcessStoreStatements.java | 4 + .../postgres/TransferProcessMapping.java | 2 + .../PostgresTransferProcessStoreTest.java | 18 +- .../eclipse/edc/spi/entity/PendingGuard.java | 28 +++ .../edc/spi/entity/StatefulEntity.java | 15 ++ .../edc/spi/persistence/StateEntityStore.java | 4 + .../ContractNegotiationConfirmed.java | 56 ------ .../ContractNegotiationDeclined.java | 57 ------ .../ContractNegotiationFailed.java | 57 ------ .../ContractNegotiationPendingGuard.java | 28 +++ .../observe/ContractNegotiationListener.java | 22 --- .../contract/spi/event/ContractEventTest.java | 6 - .../ContractNegotiationStoreTestBase.java | 42 +++-- .../negotiation/store/TestFunctions.java | 12 +- .../spi/TransferProcessPendingGuard.java | 28 +++ .../spi/event/TransferProcessCancelled.java | 56 ------ .../spi/event/TransferProcessEnded.java | 56 ------ .../store/TransferProcessStoreTestBase.java | 166 +++++++++--------- .../TransferProcessApiEndToEndTest.java | 14 +- 54 files changed, 1087 insertions(+), 727 deletions(-) rename core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/{StateProcessor.java => Processor.java} (96%) create mode 100644 core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/ProcessorImpl.java create mode 100644 core/common/state-machine/src/test/java/org/eclipse/edc/statemachine/ProcessorImplTest.java create mode 100644 core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractNegotiationDefaultServicesExtension.java create mode 100644 core/control-plane/transfer-core/src/main/java/org/eclipse/edc/connector/transfer/TransferProcessDefaultServicesExtension.java create mode 100644 extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/ColumnEntry.java create mode 100644 extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/SqlExecuteStatement.java create mode 100644 spi/common/core-spi/src/main/java/org/eclipse/edc/spi/entity/PendingGuard.java delete mode 100644 spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationConfirmed.java delete mode 100644 spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationDeclined.java delete mode 100644 spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/event/contractnegotiation/ContractNegotiationFailed.java create mode 100644 spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/contract/spi/negotiation/ContractNegotiationPendingGuard.java create mode 100644 spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/TransferProcessPendingGuard.java delete mode 100644 spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessCancelled.java delete mode 100644 spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/transfer/spi/event/TransferProcessEnded.java 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..ed5df5e8b7f --- /dev/null +++ b/core/common/state-machine/src/main/java/org/eclipse/edc/statemachine/ProcessorImpl.java @@ -0,0 +1,94 @@ +/* + * 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 -> { + if (guard.predicate().test(entity)) { + return guard.process().apply(entity); + } else { + return 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..9f60daea456 --- /dev/null +++ b/core/control-plane/contract-core/src/main/java/org/eclipse/edc/connector/contract/ContractNegotiationDefaultServicesExtension.java @@ -0,0 +1,70 @@ +/* + * 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; + +@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..84d85fb0c96 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` can be specified a `Guard`, 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..6da511b1312 --- /dev/null +++ b/extensions/common/sql/sql-core/src/main/java/org/eclipse/edc/sql/statement/SqlExecuteStatement.java @@ -0,0 +1,65 @@ +/* + * 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; + +public class SqlExecuteStatement { + + private final List columnEntries = new ArrayList<>(); + private final String jsonCastOperator; + + public SqlExecuteStatement(String jsonCastOperator) { + this.jsonCastOperator = jsonCastOperator; + } + + public static SqlExecuteStatement newInstance(String jsonCastOperator) { + return new SqlExecuteStatement(jsonCastOperator); + } + + public SqlExecuteStatement add(ColumnEntry columnEntry) { + columnEntries.add(columnEntry); + return this; + } + + public SqlExecuteStatement column(String columnName) { + columnEntries.add(standardColumn(columnName)); + return this; + } + + public SqlExecuteStatement jsonColumn(String columnName) { + columnEntries.add(ColumnEntry.jsonColumn(columnName, jsonCastOperator)); + return this; + } + + 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()); + } + + 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 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") From f2cf806f737d645ff5e278ab217670cc012a2ab1 Mon Sep 17 00:00:00 2001 From: ndr_brt Date: Tue, 25 Jul 2023 13:59:30 +0200 Subject: [PATCH 2/2] PR remarks --- .../edc/statemachine/ProcessorImpl.java | 10 ++--- ...ctNegotiationDefaultServicesExtension.java | 3 ++ docs/developer/state-machine.md | 4 +- .../sql/statement/SqlExecuteStatement.java | 41 +++++++++++++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) 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 index ed5df5e8b7f..d99f3f62e4e 100644 --- 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 @@ -46,13 +46,9 @@ private ProcessorImpl(Supplier> entitiesSupplier) { @Override public Long process() { return entities.get().stream() - .map(entity -> { - if (guard.predicate().test(entity)) { - return guard.process().apply(entity); - } else { - return process.apply(entity); - } - }) + .map(entity -> guard.predicate().test(entity) + ? guard.process().apply(entity) : + process.apply(entity)) .filter(isEqual(true)) .count(); } 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 index 9f60daea456..7f32d58231b 100644 --- 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 @@ -31,6 +31,9 @@ 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 { diff --git a/docs/developer/state-machine.md b/docs/developer/state-machine.md index 84d85fb0c96..7987ecd83ad 100644 --- a/docs/developer/state-machine.md +++ b/docs/developer/state-machine.md @@ -66,8 +66,8 @@ public class EntityManager { ``` ### Guards -On a state machine `Processor` can be specified a `Guard`, 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 +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. 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 index 6da511b1312..c242103910a 100644 --- 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 @@ -21,6 +21,9 @@ 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<>(); @@ -30,31 +33,69 @@ 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())