@twin.org/dataspace-control-plane-service 0.9.1-next.4 → 0.9.1-next.6
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/dataspaceControlPlaneRoutes.js +9 -5
- package/dist/es/dataspaceControlPlaneRoutes.js.map +1 -1
- package/dist/es/dataspaceControlPlaneService.js +99 -282
- 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 +112 -0
- package/dist/es/handlers/httpDataPostTransferHandler.js.map +1 -0
- package/dist/es/handlers/httpDataPullTransferHandler.js +109 -0
- package/dist/es/handlers/httpDataPullTransferHandler.js.map +1 -0
- package/dist/es/handlers/httpDataPushTransferHandler.js +118 -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/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/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 +28 -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/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
|
|
@@ -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.
|
|
@@ -576,23 +574,15 @@ export class DataspaceControlPlaneService {
|
|
|
576
574
|
callbackAddress,
|
|
577
575
|
format
|
|
578
576
|
};
|
|
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
|
-
}
|
|
577
|
+
// Delegate consumer dataAddress construction to the format-specific handler.
|
|
578
|
+
// PUSH supplies its /inbox; PULL and POST return undefined (no consumer address needed).
|
|
579
|
+
const transferHandler = TransferHandlerFactory.get(format);
|
|
580
|
+
transferRequestMessage.dataAddress = transferHandler.buildConsumerDataAddress({
|
|
581
|
+
consumerPid,
|
|
582
|
+
origin,
|
|
583
|
+
dataPlanePath: this._dataPlanePath,
|
|
584
|
+
organizationIdentity
|
|
585
|
+
});
|
|
596
586
|
// Generate outbound trust token to authenticate this node to the provider.
|
|
597
587
|
const outboundToken = await this._trustComponent.generate(organizationIdentity, this._overrideTrustGeneratorType, { subject: { consumerPid, agreementId } });
|
|
598
588
|
// Create a remote REST client pointed at the provider endpoint and call requestTransfer.
|
|
@@ -726,200 +716,32 @@ export class DataspaceControlPlaneService {
|
|
|
726
716
|
consumerPid: entity.consumerPid,
|
|
727
717
|
providerPid: entity.providerPid
|
|
728
718
|
};
|
|
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
|
-
});
|
|
719
|
+
// Delegate dataAddress construction and post-start actions to the format-specific handler.
|
|
720
|
+
// Push subscription setup reads the entity from storage and requires state=STARTED,
|
|
721
|
+
// so STARTED is persisted before calling onProviderStart. If subscription setup fails,
|
|
722
|
+
// the state is rolled back so the client can retry.
|
|
723
|
+
const organizationIdentity = await this.resolveContextOrganizationId();
|
|
724
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
725
|
+
response.dataAddress = await transferHandler.buildProviderStartDataAddress({
|
|
726
|
+
entity,
|
|
727
|
+
publicOrigin,
|
|
728
|
+
dataPlanePath: this._dataPlanePath,
|
|
729
|
+
organizationIdentity,
|
|
730
|
+
trustComponent: this._trustComponent,
|
|
731
|
+
overrideTrustGeneratorType: this._overrideTrustGeneratorType
|
|
732
|
+
});
|
|
733
|
+
entity.state = DataspaceProtocolTransferProcessStateType.STARTED;
|
|
734
|
+
entity.dateModified = new Date();
|
|
735
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
736
|
+
try {
|
|
737
|
+
await transferHandler.onProviderStart(this._dataPlaneComponent, entity.consumerPid, previousState);
|
|
861
738
|
}
|
|
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;
|
|
739
|
+
catch (subscriptionError) {
|
|
740
|
+
entity.state = previousState;
|
|
903
741
|
entity.dateModified = new Date();
|
|
904
742
|
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
|
-
}
|
|
743
|
+
throw subscriptionError;
|
|
919
744
|
}
|
|
920
|
-
entity.state = DataspaceProtocolTransferProcessStateType.STARTED;
|
|
921
|
-
entity.dateModified = new Date();
|
|
922
|
-
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
923
745
|
await this._loggingComponent?.log({
|
|
924
746
|
level: "info",
|
|
925
747
|
source: DataspaceControlPlaneService.CLASS_NAME,
|
|
@@ -1050,19 +872,18 @@ export class DataspaceControlPlaneService {
|
|
|
1050
872
|
role
|
|
1051
873
|
}
|
|
1052
874
|
});
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
875
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
876
|
+
try {
|
|
877
|
+
await transferHandler.onComplete(this._dataPlaneComponent, entity.consumerPid);
|
|
878
|
+
}
|
|
879
|
+
catch (teardownError) {
|
|
880
|
+
// Symmetric rollback: data-plane teardown failed, revert the transfer state
|
|
881
|
+
// so the client can retry. Without this the storage row is COMPLETED but
|
|
882
|
+
// the subscription is still flowing, and a retry hits invalidStateForComplete.
|
|
883
|
+
entity.state = previousState;
|
|
884
|
+
entity.dateModified = new Date();
|
|
885
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
886
|
+
throw teardownError;
|
|
1066
887
|
}
|
|
1067
888
|
// Completion is consumer-initiated (the auth above accepts only the consumer): a consumer→provider
|
|
1068
889
|
// notification, so no provider→consumer delivery here — unlike suspend/terminate (either party).
|
|
@@ -1147,18 +968,17 @@ export class DataspaceControlPlaneService {
|
|
|
1147
968
|
reason: message.reason
|
|
1148
969
|
}
|
|
1149
970
|
});
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
}
|
|
971
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
972
|
+
try {
|
|
973
|
+
await transferHandler.onSuspend(this._dataPlaneComponent, entity.consumerPid);
|
|
974
|
+
}
|
|
975
|
+
catch (suspendError) {
|
|
976
|
+
// Symmetric rollback: data-plane suspend failed, revert transfer state so
|
|
977
|
+
// a retry can repair the subscription instead of failing invalidStateForSuspend.
|
|
978
|
+
entity.state = previousState;
|
|
979
|
+
entity.dateModified = new Date();
|
|
980
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
981
|
+
throw suspendError;
|
|
1162
982
|
}
|
|
1163
983
|
if (role === TransferProcessRole.Consumer) {
|
|
1164
984
|
await this._internalTransferCallback.onStateChanged(entity.consumerPid, DataspaceProtocolTransferProcessStateType.SUSPENDED);
|
|
@@ -1243,19 +1063,18 @@ export class DataspaceControlPlaneService {
|
|
|
1243
1063
|
reason: message.reason
|
|
1244
1064
|
}
|
|
1245
1065
|
});
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
}
|
|
1066
|
+
const transferHandler = TransferHandlerFactory.get(this.inferTransferFormat(entity));
|
|
1067
|
+
try {
|
|
1068
|
+
await transferHandler.onTerminate(this._dataPlaneComponent, entity.consumerPid);
|
|
1069
|
+
}
|
|
1070
|
+
catch (teardownError) {
|
|
1071
|
+
// Symmetric rollback: data-plane teardown failed, revert transfer state so
|
|
1072
|
+
// a retry can repair the subscription. Terminate is reachable from multiple
|
|
1073
|
+
// states (REQUESTED/STARTED/SUSPENDED), so restore the actual previous one.
|
|
1074
|
+
entity.state = previousState;
|
|
1075
|
+
entity.dateModified = new Date();
|
|
1076
|
+
await this._transferProcessStorage.set(this.modelToStorageEntity(entity));
|
|
1077
|
+
throw teardownError;
|
|
1259
1078
|
}
|
|
1260
1079
|
if (role === TransferProcessRole.Consumer) {
|
|
1261
1080
|
await this._internalTransferCallback.onStateChanged(entity.consumerPid, DataspaceProtocolTransferProcessStateType.TERMINATED);
|
|
@@ -2168,16 +1987,6 @@ export class DataspaceControlPlaneService {
|
|
|
2168
1987
|
}
|
|
2169
1988
|
return agreement;
|
|
2170
1989
|
}
|
|
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
1990
|
/**
|
|
2182
1991
|
* Extract the dataset ID from an ODRL agreement's target.
|
|
2183
1992
|
* @param agreement The ODRL agreement containing the target.
|
|
@@ -2433,20 +2242,6 @@ export class DataspaceControlPlaneService {
|
|
|
2433
2242
|
}
|
|
2434
2243
|
return dataset;
|
|
2435
2244
|
}
|
|
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
2245
|
/**
|
|
2451
2246
|
* Creates the internal INegotiationCallback that fans out to all registered callbacks.
|
|
2452
2247
|
* @returns The internal negotiation callback.
|
|
@@ -2658,6 +2453,28 @@ export class DataspaceControlPlaneService {
|
|
|
2658
2453
|
const contextIds = await ContextIdStore.getContextIds();
|
|
2659
2454
|
return contextIds?.[ContextIdKeys.Tenant];
|
|
2660
2455
|
}
|
|
2456
|
+
/**
|
|
2457
|
+
* Resolve the transfer format for handler dispatch. When the entity already carries a
|
|
2458
|
+
* format string (all transfers created by requestTransfer), that value is used directly.
|
|
2459
|
+
* For entities seeded without a format (e.g. older storage records), the format is inferred
|
|
2460
|
+
* from the presence of a consumer-supplied dataAddress — matching the pre-factory dispatch
|
|
2461
|
+
* logic so existing records continue to work correctly.
|
|
2462
|
+
* @param entity The transfer process entity.
|
|
2463
|
+
* @returns The effective DataspaceTransferFormat for handler lookup.
|
|
2464
|
+
* @internal
|
|
2465
|
+
*/
|
|
2466
|
+
inferTransferFormat(entity) {
|
|
2467
|
+
const knownFormats = Object.values(DataspaceTransferFormat);
|
|
2468
|
+
if (Is.stringValue(entity.format) && knownFormats.includes(entity.format)) {
|
|
2469
|
+
return entity.format;
|
|
2470
|
+
}
|
|
2471
|
+
// Backward-compat: entities without a recognized format string (e.g. stored before
|
|
2472
|
+
// the factory was introduced, or using a non-standard format value) fall back to
|
|
2473
|
+
// dataAddress-based dispatch matching the pre-factory logic.
|
|
2474
|
+
return Is.empty(entity.dataAddress)
|
|
2475
|
+
? DataspaceTransferFormat.HttpDataPull
|
|
2476
|
+
: DataspaceTransferFormat.HttpDataPush;
|
|
2477
|
+
}
|
|
2661
2478
|
/**
|
|
2662
2479
|
* Return an existing full-access agreement for the same-organization (implicit trust) case,
|
|
2663
2480
|
* or create and store one if none exists. Fires onFinalized on all registered callbacks in
|