@twin.org/dataspace-control-plane-service 0.9.1-next.5 → 0.9.1-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/dataspaceControlPlaneService.js +101 -289
- package/dist/es/dataspaceControlPlaneService.js.map +1 -1
- package/dist/es/factories/transferHandlerFactory.js +11 -0
- package/dist/es/factories/transferHandlerFactory.js.map +1 -0
- package/dist/es/handlers/httpDataPostTransferHandler.js +111 -0
- package/dist/es/handlers/httpDataPostTransferHandler.js.map +1 -0
- package/dist/es/handlers/httpDataPullTransferHandler.js +108 -0
- package/dist/es/handlers/httpDataPullTransferHandler.js.map +1 -0
- package/dist/es/handlers/httpDataPushTransferHandler.js +114 -0
- package/dist/es/handlers/httpDataPushTransferHandler.js.map +1 -0
- package/dist/es/index.js +7 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IDataspaceControlPlaneServiceConfig.js.map +1 -1
- package/dist/es/models/ITransferHandler.js +2 -0
- package/dist/es/models/ITransferHandler.js.map +1 -0
- package/dist/es/models/ITransferHandlerPrepareContext.js +4 -0
- package/dist/es/models/ITransferHandlerPrepareContext.js.map +1 -0
- package/dist/es/models/ITransferHandlerStartContext.js +2 -0
- package/dist/es/models/ITransferHandlerStartContext.js.map +1 -0
- package/dist/types/factories/transferHandlerFactory.d.ts +8 -0
- package/dist/types/handlers/httpDataPostTransferHandler.d.ts +63 -0
- package/dist/types/handlers/httpDataPullTransferHandler.d.ts +63 -0
- package/dist/types/handlers/httpDataPushTransferHandler.d.ts +68 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/models/IDataspaceControlPlaneServiceConfig.d.ts +10 -5
- package/dist/types/models/ITransferHandler.d.ts +62 -0
- package/dist/types/models/ITransferHandlerPrepareContext.d.ts +21 -0
- package/dist/types/models/ITransferHandlerStartContext.d.ts +31 -0
- package/docs/changelog.md +33 -0
- package/docs/reference/classes/HttpDataPostTransferHandler.md +235 -0
- package/docs/reference/classes/HttpDataPullTransferHandler.md +235 -0
- package/docs/reference/classes/HttpDataPushTransferHandler.md +242 -0
- package/docs/reference/index.md +7 -0
- package/docs/reference/interfaces/IDataspaceControlPlaneServiceConfig.md +10 -5
- package/docs/reference/interfaces/ITransferHandler.md +180 -0
- package/docs/reference/interfaces/ITransferHandlerPrepareContext.md +35 -0
- package/docs/reference/interfaces/ITransferHandlerStartContext.md +51 -0
- package/docs/reference/variables/TransferHandlerFactory.md +7 -0
- package/locales/en.json +13 -7
- package/package.json +2 -2
|
@@ -9,12 +9,15 @@ import { EngineCoreFactory } from "@twin.org/engine-models";
|
|
|
9
9
|
import { ComparisonOperator } from "@twin.org/entity";
|
|
10
10
|
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
11
11
|
import { OdrlPolicyHelper, PolicyRequesterFactory } from "@twin.org/rights-management-models";
|
|
12
|
-
import { DataspaceProtocolContexts, DataspaceProtocolContractNegotiationTypes,
|
|
12
|
+
import { DataspaceProtocolContexts, DataspaceProtocolContractNegotiationTypes, DataspaceProtocolHelper, DataspaceProtocolTransferProcessStateType, DataspaceProtocolTransferProcessTypes, DataspaceProtocolVersionBindingType } from "@twin.org/standards-dataspace-protocol";
|
|
13
13
|
import { OdrlActionType, OdrlContexts, OdrlPolicyType } from "@twin.org/standards-w3c-odrl";
|
|
14
14
|
import { MetricHelper } from "@twin.org/telemetry-models";
|
|
15
15
|
import { TrustHelper } from "@twin.org/trust-models";
|
|
16
16
|
import { DataspaceControlPlanePolicyRequester } from "./dataspaceControlPlanePolicyRequester.js";
|
|
17
|
-
import {
|
|
17
|
+
import { TransferHandlerFactory } from "./factories/transferHandlerFactory.js";
|
|
18
|
+
import { HttpDataPostTransferHandler } from "./handlers/httpDataPostTransferHandler.js";
|
|
19
|
+
import { HttpDataPullTransferHandler } from "./handlers/httpDataPullTransferHandler.js";
|
|
20
|
+
import { HttpDataPushTransferHandler } from "./handlers/httpDataPushTransferHandler.js";
|
|
18
21
|
import { isCatalogError, isCatalogErrorName, transformToTransferError } from "./utils/transferErrorUtils.js";
|
|
19
22
|
/**
|
|
20
23
|
* Dataspace Control Plane Service implementation.
|
|
@@ -108,11 +111,10 @@ export class DataspaceControlPlaneService {
|
|
|
108
111
|
*/
|
|
109
112
|
_callbackPath;
|
|
110
113
|
/**
|
|
111
|
-
*
|
|
112
|
-
* (not at construction) so the data plane may register after the control plane is built.
|
|
114
|
+
* Data plane component.
|
|
113
115
|
* @internal
|
|
114
116
|
*/
|
|
115
|
-
|
|
117
|
+
_dataPlaneComponent;
|
|
116
118
|
/**
|
|
117
119
|
* Policy requester instance for handling negotiation callbacks.
|
|
118
120
|
* @internal
|
|
@@ -190,7 +192,7 @@ export class DataspaceControlPlaneService {
|
|
|
190
192
|
this._trustComponent = ComponentFactory.get(options?.trustComponentType ?? "trust");
|
|
191
193
|
this._overrideTrustGeneratorType = options?.config?.overrideTrustGeneratorType;
|
|
192
194
|
this._dataPlanePath = Is.stringValue(options?.config?.dataPlanePath)
|
|
193
|
-
? StringHelper.
|
|
195
|
+
? StringHelper.trimLeadingAndTrailingSlashes(options.config.dataPlanePath)
|
|
194
196
|
: undefined;
|
|
195
197
|
this._callbackPath = Is.stringValue(options?.config?.callbackPath)
|
|
196
198
|
? StringHelper.trimLeadingAndTrailingSlashes(options.config.callbackPath)
|
|
@@ -205,14 +207,7 @@ export class DataspaceControlPlaneService {
|
|
|
205
207
|
this._taskScheduler = ComponentFactory.getIfExists(options?.taskSchedulerComponentType ?? "task-scheduler");
|
|
206
208
|
this._platformComponent = ComponentFactory.get(options?.platformComponentType ?? "platform");
|
|
207
209
|
this._telemetryComponent = ComponentFactory.getIfExists(options?.telemetryComponentType);
|
|
208
|
-
|
|
209
|
-
// BEFORE the data plane in engine startup order, so `getRegisteredInstanceTypeOptional`
|
|
210
|
-
// returns undefined when the engine constructs us — the wiring override can't fire here.
|
|
211
|
-
// The default therefore matches the engine's actual factory key
|
|
212
|
-
// (`nameofKebabCase(DataspaceDataPlaneService)`) so the late-bound `requireDataPlane()`
|
|
213
|
-
// lookup at push time finds it regardless of init order.
|
|
214
|
-
this._dataPlaneComponentType =
|
|
215
|
-
options?.dataPlaneComponentType ?? "dataspace-data-plane-service";
|
|
210
|
+
this._dataPlaneComponent = ComponentFactory.get(options?.dataPlaneComponentType ?? "dataspace-data-plane-service");
|
|
216
211
|
this._negotiationCallbacks = new Map();
|
|
217
212
|
this._transferCallbacks = new Map();
|
|
218
213
|
this._internalTransferCallback = this.createInternalTransferCallback();
|
|
@@ -221,6 +216,9 @@ export class DataspaceControlPlaneService {
|
|
|
221
216
|
const internalCallback = this.createInternalCallback();
|
|
222
217
|
this._policyRequester = new DataspaceControlPlanePolicyRequester(options?.loggingComponentType, internalCallback);
|
|
223
218
|
PolicyRequesterFactory.register(DataspaceControlPlaneService._REQUESTER_TYPE, () => this._policyRequester);
|
|
219
|
+
TransferHandlerFactory.register(DataspaceTransferFormat.HttpDataPull, () => new HttpDataPullTransferHandler());
|
|
220
|
+
TransferHandlerFactory.register(DataspaceTransferFormat.HttpDataPush, () => new HttpDataPushTransferHandler());
|
|
221
|
+
TransferHandlerFactory.register(DataspaceTransferFormat.HttpDataPost, () => new HttpDataPostTransferHandler());
|
|
224
222
|
}
|
|
225
223
|
/**
|
|
226
224
|
* Returns the class name of the component.
|
|
@@ -562,12 +560,7 @@ export class DataspaceControlPlaneService {
|
|
|
562
560
|
// Callback the provider POSTs DSP messages back to: mount path + `?organization=` so the inbound
|
|
563
561
|
// POST routes to the right consumer tenant (mirrors buildCallbackUrl). The bare `origin` is kept for
|
|
564
562
|
// the PUSH-inbox base below (a different mount).
|
|
565
|
-
|
|
566
|
-
? `${origin}/${this._callbackPath}`
|
|
567
|
-
: origin;
|
|
568
|
-
if (Is.stringValue(organizationIdentity)) {
|
|
569
|
-
callbackAddress = HttpUrlHelper.addQueryStringParam(callbackAddress, ContextIdKeys.Organization, organizationIdentity);
|
|
570
|
-
}
|
|
563
|
+
const callbackAddress = HttpUrlHelper.addQueryStringParam(Is.stringValue(this._callbackPath) ? `${origin}/${this._callbackPath}` : origin, ContextIdKeys.Organization, organizationIdentity);
|
|
571
564
|
const transferRequestMessage = {
|
|
572
565
|
"@context": [DataspaceProtocolContexts.Context],
|
|
573
566
|
"@type": DataspaceProtocolTransferProcessTypes.TransferRequestMessage,
|
|
@@ -576,23 +569,15 @@ export class DataspaceControlPlaneService {
|
|
|
576
569
|
callbackAddress,
|
|
577
570
|
format
|
|
578
571
|
};
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
inboxEndpoint = HttpUrlHelper.addQueryStringParam(inboxEndpoint, ContextIdKeys.Organization, organizationIdentity);
|
|
589
|
-
}
|
|
590
|
-
transferRequestMessage.dataAddress = {
|
|
591
|
-
"@type": DataspaceProtocolTransferProcessTypes.DataAddress,
|
|
592
|
-
endpointType: DataspaceProtocolEndpointType.HttpsActivityStreamEndpoint,
|
|
593
|
-
endpoint: inboxEndpoint
|
|
594
|
-
};
|
|
595
|
-
}
|
|
572
|
+
// Delegate consumer dataAddress construction to the format-specific handler.
|
|
573
|
+
// PUSH supplies its /inbox; PULL and POST return undefined (no consumer address needed).
|
|
574
|
+
const transferHandler = TransferHandlerFactory.get(format);
|
|
575
|
+
transferRequestMessage.dataAddress = transferHandler.buildConsumerDataAddress({
|
|
576
|
+
consumerPid,
|
|
577
|
+
origin,
|
|
578
|
+
dataPlanePath: this._dataPlanePath,
|
|
579
|
+
organizationIdentity
|
|
580
|
+
});
|
|
596
581
|
// Generate outbound trust token to authenticate this node to the provider.
|
|
597
582
|
const outboundToken = await this._trustComponent.generate(organizationIdentity, this._overrideTrustGeneratorType, { subject: { consumerPid, agreementId } });
|
|
598
583
|
// Create a remote REST client pointed at the provider endpoint and call requestTransfer.
|
|
@@ -726,200 +711,32 @@ export class DataspaceControlPlaneService {
|
|
|
726
711
|
consumerPid: entity.consumerPid,
|
|
727
712
|
providerPid: entity.providerPid
|
|
728
713
|
};
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if (Is.empty(entity.dataAddress) && entity.format === DataspaceTransferFormat.HttpDataPost) {
|
|
749
|
-
// PROVIDER-INITIATED PUSH: Consumer requested push but did not supply an /inbox.
|
|
750
|
-
// Provider returns its own /inbox URL + a signed JWT so the consumer can verify
|
|
751
|
-
// the incoming activities (HttpData-POST / DataspaceTransferFormat.HttpDataPost).
|
|
752
|
-
if (!Is.stringValue(this._dataPlanePath)) {
|
|
753
|
-
return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "pushTransferDataPathNotConfigured", { consumerPid: entity.consumerPid }), message);
|
|
754
|
-
}
|
|
755
|
-
if (!Is.stringValue(entity.providerIdentity)) {
|
|
756
|
-
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "providerIdentityMissing");
|
|
757
|
-
}
|
|
758
|
-
const pushProviderId = entity.providerIdentity;
|
|
759
|
-
const accessToken = await this._trustComponent.generate(pushProviderId, this._overrideTrustGeneratorType, {
|
|
760
|
-
subject: {
|
|
761
|
-
consumerPid: entity.consumerPid,
|
|
762
|
-
providerPid: entity.providerPid,
|
|
763
|
-
agreementId: entity.agreementId,
|
|
764
|
-
datasetId: entity.datasetId
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "accessToken", accessToken);
|
|
768
|
-
const tokenString = accessToken;
|
|
769
|
-
let fullEndpoint = `${publicOrigin}/${this._dataPlanePath}/inbox`;
|
|
770
|
-
// Bake the provider's organization identity into the /inbox URL so the consumer's
|
|
771
|
-
// inbound POST routes to the right organization via TenantProcessor — mirrors the
|
|
772
|
-
// pull-mode endpoint baking below.
|
|
773
|
-
const organizationIdentity = await this.resolveContextOrganizationId();
|
|
774
|
-
fullEndpoint = HttpUrlHelper.addQueryStringParam(fullEndpoint, ContextIdKeys.Organization, organizationIdentity);
|
|
775
|
-
response.dataAddress = {
|
|
776
|
-
"@type": DataspaceProtocolTransferProcessTypes.DataAddress,
|
|
777
|
-
endpointType: DataspaceProtocolEndpointType.HttpsActivityStreamEndpoint,
|
|
778
|
-
endpoint: fullEndpoint,
|
|
779
|
-
endpointProperties: [
|
|
780
|
-
{
|
|
781
|
-
"@type": DataspaceProtocolTransferProcessTypes.EndpointProperty,
|
|
782
|
-
name: EndpointProperties.Authorization,
|
|
783
|
-
value: tokenString
|
|
784
|
-
},
|
|
785
|
-
{
|
|
786
|
-
"@type": DataspaceProtocolTransferProcessTypes.EndpointProperty,
|
|
787
|
-
name: EndpointProperties.AuthType,
|
|
788
|
-
value: "bearer"
|
|
789
|
-
}
|
|
790
|
-
]
|
|
791
|
-
};
|
|
792
|
-
await this._loggingComponent?.log({
|
|
793
|
-
level: "info",
|
|
794
|
-
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
795
|
-
ts: Date.now(),
|
|
796
|
-
message: "pushTransferStarted",
|
|
797
|
-
data: {
|
|
798
|
-
consumerPid: entity.consumerPid,
|
|
799
|
-
providerPid: entity.providerPid,
|
|
800
|
-
endpoint: fullEndpoint,
|
|
801
|
-
transferMode: DataspaceTransferFormat.HttpDataPost
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
else if (Is.empty(entity.dataAddress)) {
|
|
806
|
-
if (!Is.stringValue(this._dataPlanePath)) {
|
|
807
|
-
return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "pullTransfersNotSupported", {
|
|
808
|
-
consumerPid: entity.consumerPid,
|
|
809
|
-
providerPid: entity.providerPid
|
|
810
|
-
}), message);
|
|
811
|
-
}
|
|
812
|
-
// Provider signs the data access token with its own identity.
|
|
813
|
-
// The subject contains the transfer context claims that the data plane
|
|
814
|
-
// will verify when the consumer presents this token
|
|
815
|
-
if (!Is.stringValue(entity.providerIdentity)) {
|
|
816
|
-
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "providerIdentityMissing");
|
|
817
|
-
}
|
|
818
|
-
const pullProviderId = entity.providerIdentity;
|
|
819
|
-
const accessToken = await this._trustComponent.generate(pullProviderId, this._overrideTrustGeneratorType, {
|
|
820
|
-
subject: {
|
|
821
|
-
consumerPid: entity.consumerPid,
|
|
822
|
-
providerPid: entity.providerPid,
|
|
823
|
-
agreementId: entity.agreementId,
|
|
824
|
-
datasetId: entity.datasetId
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
|
-
Guards.stringValue(DataspaceControlPlaneService.CLASS_NAME, "accessToken", accessToken);
|
|
828
|
-
const tokenString = accessToken;
|
|
829
|
-
let fullEndpoint = `${publicOrigin}/${this._dataPlanePath}`;
|
|
830
|
-
const organizationIdentity = await this.resolveContextOrganizationId();
|
|
831
|
-
fullEndpoint = HttpUrlHelper.addQueryStringParam(fullEndpoint, ContextIdKeys.Organization, organizationIdentity);
|
|
832
|
-
response.dataAddress = {
|
|
833
|
-
"@type": DataspaceProtocolTransferProcessTypes.DataAddress,
|
|
834
|
-
endpointType: DataspaceProtocolEndpointType.HttpsQueryEndpoint,
|
|
835
|
-
endpoint: fullEndpoint,
|
|
836
|
-
endpointProperties: [
|
|
837
|
-
{
|
|
838
|
-
"@type": DataspaceProtocolTransferProcessTypes.EndpointProperty,
|
|
839
|
-
name: EndpointProperties.Authorization,
|
|
840
|
-
value: tokenString
|
|
841
|
-
},
|
|
842
|
-
{
|
|
843
|
-
"@type": DataspaceProtocolTransferProcessTypes.EndpointProperty,
|
|
844
|
-
name: EndpointProperties.AuthType,
|
|
845
|
-
value: "bearer"
|
|
846
|
-
}
|
|
847
|
-
]
|
|
848
|
-
};
|
|
849
|
-
await this._loggingComponent?.log({
|
|
850
|
-
level: "info",
|
|
851
|
-
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
852
|
-
ts: Date.now(),
|
|
853
|
-
message: "dataAccessTokenGenerated",
|
|
854
|
-
data: {
|
|
855
|
-
consumerPid: entity.consumerPid,
|
|
856
|
-
providerPid: entity.providerPid,
|
|
857
|
-
endpoint: fullEndpoint,
|
|
858
|
-
transferMode: "PULL"
|
|
859
|
-
}
|
|
860
|
-
});
|
|
714
|
+
// Delegate dataAddress construction and post-start actions to the format-specific handler.
|
|
715
|
+
// Push subscription setup reads the entity from storage and requires state=STARTED,
|
|
716
|
+
// so STARTED is persisted before calling onProviderStart. If subscription setup fails,
|
|
717
|
+
// the state is rolled back so the client can retry.
|
|
718
|
+
const organizationIdentity = await this.resolveContextOrganizationId();
|
|
719
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
720
|
+
response.dataAddress = await transferHandler.buildProviderStartDataAddress({
|
|
721
|
+
entity,
|
|
722
|
+
publicOrigin,
|
|
723
|
+
dataPlanePath: this._dataPlanePath,
|
|
724
|
+
organizationIdentity,
|
|
725
|
+
trustComponent: this._trustComponent,
|
|
726
|
+
overrideTrustGeneratorType: this._overrideTrustGeneratorType
|
|
727
|
+
});
|
|
728
|
+
entity.state = DataspaceProtocolTransferProcessStateType.STARTED;
|
|
729
|
+
entity.dateModified = new Date();
|
|
730
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
731
|
+
try {
|
|
732
|
+
await transferHandler.onProviderStart(this._dataPlaneComponent, entity.consumerPid, previousState);
|
|
861
733
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
// the TransferRequestMessage.dataAddress. Provider responds with its own /inbox
|
|
865
|
-
// URL so the consumer knows where to route data notifications.
|
|
866
|
-
if (!Is.stringValue(entity.dataAddress?.endpoint) ||
|
|
867
|
-
!Is.stringValue(entity.dataAddress?.endpointType)) {
|
|
868
|
-
return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "invalidPushDataAddress", {
|
|
869
|
-
consumerPid: entity.consumerPid
|
|
870
|
-
}), message);
|
|
871
|
-
}
|
|
872
|
-
if (!Is.stringValue(this._dataPlanePath)) {
|
|
873
|
-
return transformToTransferError(new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "pushTransferDataPathNotConfigured", { consumerPid: entity.consumerPid }), message);
|
|
874
|
-
}
|
|
875
|
-
let fullEndpoint = `${publicOrigin}/${this._dataPlanePath}/inbox`;
|
|
876
|
-
// Bake the provider's organization identity into the /inbox URL so the consumer's
|
|
877
|
-
// inbound POST routes to the right organization via TenantProcessor — mirrors the
|
|
878
|
-
// pull-mode endpoint baking.
|
|
879
|
-
const organizationIdentity = await this.resolveContextOrganizationId();
|
|
880
|
-
fullEndpoint = HttpUrlHelper.addQueryStringParam(fullEndpoint, ContextIdKeys.Organization, organizationIdentity);
|
|
881
|
-
response.dataAddress = {
|
|
882
|
-
"@type": DataspaceProtocolTransferProcessTypes.DataAddress,
|
|
883
|
-
endpointType: DataspaceProtocolEndpointType.HttpsActivityStreamEndpoint,
|
|
884
|
-
endpoint: fullEndpoint
|
|
885
|
-
};
|
|
886
|
-
await this._loggingComponent?.log({
|
|
887
|
-
level: "info",
|
|
888
|
-
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
889
|
-
ts: Date.now(),
|
|
890
|
-
message: "pushTransferStarted",
|
|
891
|
-
data: {
|
|
892
|
-
consumerPid: entity.consumerPid,
|
|
893
|
-
providerPid: entity.providerPid,
|
|
894
|
-
endpoint: fullEndpoint,
|
|
895
|
-
transferMode: DataspaceTransferFormat.HttpDataPush
|
|
896
|
-
}
|
|
897
|
-
});
|
|
898
|
-
const dataPlane = this.requireDataPlane();
|
|
899
|
-
// Push subscription setup reads the entity from storage and requires state=STARTED.
|
|
900
|
-
// Persist STARTED before the data-plane call, and roll back if subscription setup
|
|
901
|
-
// fails so the row doesn't leak to STARTED on a setup-time error.
|
|
902
|
-
entity.state = DataspaceProtocolTransferProcessStateType.STARTED;
|
|
734
|
+
catch (subscriptionError) {
|
|
735
|
+
entity.state = previousState;
|
|
903
736
|
entity.dateModified = new Date();
|
|
904
737
|
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
905
|
-
|
|
906
|
-
if (previousState === DataspaceProtocolTransferProcessStateType.REQUESTED) {
|
|
907
|
-
await dataPlane.setupPushSubscription(entity.consumerPid);
|
|
908
|
-
}
|
|
909
|
-
else if (previousState === DataspaceProtocolTransferProcessStateType.SUSPENDED) {
|
|
910
|
-
await dataPlane.resumePushSubscription(entity.consumerPid);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
catch (subscriptionError) {
|
|
914
|
-
entity.state = previousState;
|
|
915
|
-
entity.dateModified = new Date();
|
|
916
|
-
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
917
|
-
throw subscriptionError;
|
|
918
|
-
}
|
|
738
|
+
throw subscriptionError;
|
|
919
739
|
}
|
|
920
|
-
entity.state = DataspaceProtocolTransferProcessStateType.STARTED;
|
|
921
|
-
entity.dateModified = new Date();
|
|
922
|
-
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
923
740
|
await this._loggingComponent?.log({
|
|
924
741
|
level: "info",
|
|
925
742
|
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
@@ -1050,19 +867,18 @@ export class DataspaceControlPlaneService {
|
|
|
1050
867
|
role
|
|
1051
868
|
}
|
|
1052
869
|
});
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
870
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
871
|
+
try {
|
|
872
|
+
await transferHandler.onComplete(this._dataPlaneComponent, entity.consumerPid);
|
|
873
|
+
}
|
|
874
|
+
catch (teardownError) {
|
|
875
|
+
// Symmetric rollback: data-plane teardown failed, revert the transfer state
|
|
876
|
+
// so the client can retry. Without this the storage row is COMPLETED but
|
|
877
|
+
// the subscription is still flowing, and a retry hits invalidStateForComplete.
|
|
878
|
+
entity.state = previousState;
|
|
879
|
+
entity.dateModified = new Date();
|
|
880
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
881
|
+
throw teardownError;
|
|
1066
882
|
}
|
|
1067
883
|
// Completion is consumer-initiated (the auth above accepts only the consumer): a consumer→provider
|
|
1068
884
|
// notification, so no provider→consumer delivery here — unlike suspend/terminate (either party).
|
|
@@ -1147,18 +963,17 @@ export class DataspaceControlPlaneService {
|
|
|
1147
963
|
reason: message.reason
|
|
1148
964
|
}
|
|
1149
965
|
});
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
}
|
|
966
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
967
|
+
try {
|
|
968
|
+
await transferHandler.onSuspend(this._dataPlaneComponent, entity.consumerPid);
|
|
969
|
+
}
|
|
970
|
+
catch (suspendError) {
|
|
971
|
+
// Symmetric rollback: data-plane suspend failed, revert transfer state so
|
|
972
|
+
// a retry can repair the subscription instead of failing invalidStateForSuspend.
|
|
973
|
+
entity.state = previousState;
|
|
974
|
+
entity.dateModified = new Date();
|
|
975
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
976
|
+
throw suspendError;
|
|
1162
977
|
}
|
|
1163
978
|
if (role === TransferProcessRole.Consumer) {
|
|
1164
979
|
await this._internalTransferCallback.onStateChanged(entity.consumerPid, DataspaceProtocolTransferProcessStateType.SUSPENDED);
|
|
@@ -1243,19 +1058,18 @@ export class DataspaceControlPlaneService {
|
|
|
1243
1058
|
reason: message.reason
|
|
1244
1059
|
}
|
|
1245
1060
|
});
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
}
|
|
1061
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
1062
|
+
try {
|
|
1063
|
+
await transferHandler.onTerminate(this._dataPlaneComponent, entity.consumerPid);
|
|
1064
|
+
}
|
|
1065
|
+
catch (teardownError) {
|
|
1066
|
+
// Symmetric rollback: data-plane teardown failed, revert transfer state so
|
|
1067
|
+
// a retry can repair the subscription. Terminate is reachable from multiple
|
|
1068
|
+
// states (REQUESTED/STARTED/SUSPENDED), so restore the actual previous one.
|
|
1069
|
+
entity.state = previousState;
|
|
1070
|
+
entity.dateModified = new Date();
|
|
1071
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
1072
|
+
throw teardownError;
|
|
1259
1073
|
}
|
|
1260
1074
|
if (role === TransferProcessRole.Consumer) {
|
|
1261
1075
|
await this._internalTransferCallback.onStateChanged(entity.consumerPid, DataspaceProtocolTransferProcessStateType.TERMINATED);
|
|
@@ -2168,16 +1982,6 @@ export class DataspaceControlPlaneService {
|
|
|
2168
1982
|
}
|
|
2169
1983
|
return agreement;
|
|
2170
1984
|
}
|
|
2171
|
-
/**
|
|
2172
|
-
* Check whether a transfer format string represents a push-mode delivery.
|
|
2173
|
-
* @param format The transfer format string from the TransferProcess entity.
|
|
2174
|
-
* @returns True if the format is a push variant (HttpData-PUSH or HttpData-POST).
|
|
2175
|
-
* @internal
|
|
2176
|
-
*/
|
|
2177
|
-
isPushFormat(format) {
|
|
2178
|
-
return (format === DataspaceTransferFormat.HttpDataPush ||
|
|
2179
|
-
format === DataspaceTransferFormat.HttpDataPost);
|
|
2180
|
-
}
|
|
2181
1985
|
/**
|
|
2182
1986
|
* Extract the dataset ID from an ODRL agreement's target.
|
|
2183
1987
|
* @param agreement The ODRL agreement containing the target.
|
|
@@ -2433,20 +2237,6 @@ export class DataspaceControlPlaneService {
|
|
|
2433
2237
|
}
|
|
2434
2238
|
return dataset;
|
|
2435
2239
|
}
|
|
2436
|
-
/**
|
|
2437
|
-
* Resolve the data plane component or throw if it isn't registered. Push-mode transfers
|
|
2438
|
-
* require the data plane; pull-only deployments may run without it.
|
|
2439
|
-
* @returns The data plane component.
|
|
2440
|
-
* @throws GeneralError if the data plane component is not registered.
|
|
2441
|
-
* @internal
|
|
2442
|
-
*/
|
|
2443
|
-
requireDataPlane() {
|
|
2444
|
-
const dataPlane = ComponentFactory.getIfExists(this._dataPlaneComponentType);
|
|
2445
|
-
if (!dataPlane) {
|
|
2446
|
-
throw new GeneralError(DataspaceControlPlaneService.CLASS_NAME, "dataPlaneNotRegistered");
|
|
2447
|
-
}
|
|
2448
|
-
return dataPlane;
|
|
2449
|
-
}
|
|
2450
2240
|
/**
|
|
2451
2241
|
* Creates the internal INegotiationCallback that fans out to all registered callbacks.
|
|
2452
2242
|
* @returns The internal negotiation callback.
|
|
@@ -2658,6 +2448,28 @@ export class DataspaceControlPlaneService {
|
|
|
2658
2448
|
const contextIds = await ContextIdStore.getContextIds();
|
|
2659
2449
|
return contextIds?.[ContextIdKeys.Tenant];
|
|
2660
2450
|
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Resolve the transfer format for handler dispatch. When the entity already carries a
|
|
2453
|
+
* format string (all transfers created by requestTransfer), that value is used directly.
|
|
2454
|
+
* For entities seeded without a format (e.g. older storage records), the format is inferred
|
|
2455
|
+
* from the presence of a consumer-supplied dataAddress — matching the pre-factory dispatch
|
|
2456
|
+
* logic so existing records continue to work correctly.
|
|
2457
|
+
* @param entity The transfer process entity.
|
|
2458
|
+
* @returns The effective DataspaceTransferFormat for handler lookup.
|
|
2459
|
+
* @internal
|
|
2460
|
+
*/
|
|
2461
|
+
inferTransferFormat(entity) {
|
|
2462
|
+
const knownFormats = Object.values(DataspaceTransferFormat);
|
|
2463
|
+
if (Is.stringValue(entity.format) && knownFormats.includes(entity.format)) {
|
|
2464
|
+
return entity.format;
|
|
2465
|
+
}
|
|
2466
|
+
// Backward-compat: entities without a recognized format string (e.g. stored before
|
|
2467
|
+
// the factory was introduced, or using a non-standard format value) fall back to
|
|
2468
|
+
// dataAddress-based dispatch matching the pre-factory logic.
|
|
2469
|
+
return Is.empty(entity.dataAddress)
|
|
2470
|
+
? DataspaceTransferFormat.HttpDataPull
|
|
2471
|
+
: DataspaceTransferFormat.HttpDataPush;
|
|
2472
|
+
}
|
|
2661
2473
|
/**
|
|
2662
2474
|
* Return an existing full-access agreement for the same-organization (implicit trust) case,
|
|
2663
2475
|
* or create and store one if none exists. Fires onFinalized on all registered callbacks in
|