Skip to content

Commit 60ca6d5

Browse files
authored
Merge pull request #2103 from beyonnex-io/support-updating-referenced-WoT-ThingModel
add new wot-models definition update endpoint #1843
2 parents b866582 + 89232f0 commit 60ca6d5

File tree

59 files changed

+3562
-10
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3562
-10
lines changed

base/model/src/main/java/org/eclipse/ditto/base/model/headers/AbstractDittoHeaders.java

+5
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ public boolean isDryRun() {
263263
return isExpectedBoolean(DittoHeaderDefinition.DRY_RUN, Boolean.TRUE);
264264
}
265265

266+
@Override
267+
public boolean isExternalDryRun() {
268+
return isExpectedBoolean(DittoHeaderDefinition.EXTERNAL_DRY_RUN, Boolean.TRUE);
269+
}
270+
266271
@Override
267272
public boolean isSudo() {
268273
return isExpectedBoolean(DittoHeaderDefinition.DITTO_SUDO, Boolean.TRUE);

base/model/src/main/java/org/eclipse/ditto/base/model/headers/DittoHeaderDefinition.java

+10
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ public enum DittoHeaderDefinition implements HeaderDefinition {
6868
*/
6969
DRY_RUN("ditto-dry-run", boolean.class, false, false, HeaderValueValidators.getBooleanValidator()),
7070

71+
/**
72+
* Header definition for specifying dry-run behavior from external clients.
73+
* <p>
74+
* Key: {@code "dry-run"}, Java type: {@code boolean}.
75+
* </p>
76+
*
77+
* @since 3.7.0
78+
*/
79+
EXTERNAL_DRY_RUN("dry-run", boolean.class, true, true, HeaderValueValidators.getBooleanValidator()),
80+
7181
/**
7282
* Header definition for read subjects value.
7383
* <p>

base/model/src/main/java/org/eclipse/ditto/base/model/headers/DittoHeaders.java

+8
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ static DittoHeadersBuilder newBuilder(final JsonObject jsonObject) {
197197
*/
198198
boolean isDryRun();
199199

200+
/**
201+
* Returns whether a command is to be executed as a dry-run from an external request.
202+
*
203+
* @return {@code true} if dry-run is specified externally, {@code false} otherwise.
204+
* @since 3.7.0
205+
*/
206+
boolean isExternalDryRun();
207+
200208
/**
201209
* Indicates whether this command is flagged as sudo command which should ignore some preventions.
202210
*

base/model/src/main/java/org/eclipse/ditto/base/model/signals/commands/Command.java

+7
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,13 @@ enum Category {
150150
*/
151151
MERGE,
152152

153+
/**
154+
* Category of commands that change the state of entities.
155+
*
156+
* @since 3.7.0
157+
*/
158+
MIGRATE,
159+
153160
/**
154161
* Category of commands that delete entities.
155162
*/

base/model/src/test/java/org/eclipse/ditto/base/model/headers/ImmutableDittoHeadersTest.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public final class ImmutableDittoHeadersTest {
117117
private static final String KNOWN_TRACEPARENT = "00-dfca0d990402884d22e909a87ac677ec-94fc4da95e842f96-01";
118118
private static final String KNOWN_TRACESTATE = "eclipse=ditto";
119119
private static final boolean KNOWN_DITTO_RETRIEVE_DELETED = true;
120+
private static final boolean KNOWN_DITTO_EXTERNAL_DRY_RUN = true;
120121
private static final String KNOWN_DITTO_ACKREGATOR_ADDRESS = "here!";
121122

122123
private static final String KNOWN_DITTO_GET_METADATA = "attributes/*/key";
@@ -224,6 +225,7 @@ public void settingAllKnownHeadersWorksAsExpected() {
224225
KNOWN_PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT.formatAsString())
225226
.putHeader(DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_OBJECT.getKey(),
226227
KNOWN_PRE_DEFINED_EXTRA_FIELDS_OBJECT.formatAsString())
228+
.putHeader(DittoHeaderDefinition.EXTERNAL_DRY_RUN.getKey(), String.valueOf(KNOWN_DITTO_EXTERNAL_DRY_RUN))
227229
.build();
228230

229231
assertThat(underTest).isEqualTo(expectedHeaderMap);
@@ -534,7 +536,6 @@ public void toJsonReturnsExpected() {
534536
.set(DittoHeaderDefinition.CONNECTION_ID.getKey(), KNOWN_CONNECTION_ID)
535537
.set(DittoHeaderDefinition.EXPECTED_RESPONSE_TYPES.getKey(),
536538
charSequencesToJsonArray(KNOWN_EXPECTED_RESPONSE_TYPES))
537-
.set(DittoHeaderDefinition.PUT_METADATA.getKey(), KNOWN_METADATA_HEADERS.toJson())
538539
.set(DittoHeaderDefinition.ALLOW_POLICY_LOCKOUT.getKey(), KNOWN_ALLOW_POLICY_LOCKOUT)
539540
.set(DittoHeaderDefinition.WEAK_ACK.getKey(), KNOWN_IS_WEAK_ACK)
540541
.set(DittoHeaderDefinition.EVENT_JOURNAL_TAGS.getKey(),
@@ -548,8 +549,6 @@ public void toJsonReturnsExpected() {
548549
.set(DittoHeaderDefinition.LIVE_CHANNEL_CONDITION.getKey(), KNOWN_LIVE_CHANNEL_CONDITION)
549550
.set(DittoHeaderDefinition.LIVE_CHANNEL_CONDITION_MATCHED.getKey(),
550551
KNOWN_LIVE_CHANNEL_CONDITION_MATCHED)
551-
.set(DittoHeaderDefinition.GET_METADATA.getKey(), KNOWN_DITTO_GET_METADATA)
552-
.set(DittoHeaderDefinition.DELETE_METADATA.getKey(), KNOWN_DITTO_DELETE_METADATA)
553552
.set(DittoHeaderDefinition.DITTO_METADATA.getKey(), KNOWN_DITTO_METADATA)
554553
.set(DittoHeaderDefinition.AT_HISTORICAL_REVISION.getKey(), KNOWN_AT_HISTORICAL_REVISION)
555554
.set(DittoHeaderDefinition.AT_HISTORICAL_TIMESTAMP.getKey(), KNOWN_AT_HISTORICAL_TIMESTAMP.toString())
@@ -560,6 +559,10 @@ public void toJsonReturnsExpected() {
560559
KNOWN_PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT)
561560
.set(DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_OBJECT.getKey(),
562561
KNOWN_PRE_DEFINED_EXTRA_FIELDS_OBJECT)
562+
.set(DittoHeaderDefinition.EXTERNAL_DRY_RUN.getKey(), KNOWN_DITTO_EXTERNAL_DRY_RUN)
563+
.set(DittoHeaderDefinition.PUT_METADATA.getKey(), KNOWN_METADATA_HEADERS.toJson())
564+
.set(DittoHeaderDefinition.GET_METADATA.getKey(), KNOWN_DITTO_GET_METADATA)
565+
.set(DittoHeaderDefinition.DELETE_METADATA.getKey(), KNOWN_DITTO_DELETE_METADATA)
563566
.build();
564567

565568
final Map<String, String> allKnownHeaders = createMapContainingAllKnownHeaders();
@@ -806,6 +809,8 @@ private static Map<String, String> createMapContainingAllKnownHeaders() {
806809
KNOWN_PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT.formatAsString());
807810
result.put(DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_OBJECT.getKey(),
808811
KNOWN_PRE_DEFINED_EXTRA_FIELDS_OBJECT.formatAsString());
812+
result.put(DittoHeaderDefinition.EXTERNAL_DRY_RUN.getKey(),
813+
String.valueOf(KNOWN_DITTO_EXTERNAL_DRY_RUN));
809814

810815
return result;
811816
}

documentation/src/main/resources/openapi/ditto-api-2.yml

+183
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,101 @@ paths:
739739
$ref: '#/components/responses/PreconditionFailed'
740740
'424':
741741
$ref: '#/components/responses/DependencyFailed'
742+
'/api/2/things/{thingId}/migrateDefinition':
743+
post:
744+
summary: Update the definition of an existing Thing
745+
description: |-
746+
Updates the definition of the specified thing by providing a new definition URL along with an optional migration payload.
747+
748+
The request body allows specifying:
749+
- A new Thing definition URL.
750+
- A migration payload containing updates to attributes and features.
751+
- Patch conditions to ensure consistent updates.
752+
- Whether properties should be initialized if missing.
753+
754+
If the `dry-run` query parameter or header is set to `true`, the request will return the calculated migration result without applying any changes.
755+
756+
### Example:
757+
```json
758+
{
759+
"thingDefinitionUrl": "https://example.com/new-thing-definition.json",
760+
"migrationPayload": {
761+
"attributes": {
762+
"manufacturer": "New Corp"
763+
},
764+
"features": {
765+
"sensor": {
766+
"properties": {
767+
"status": {
768+
"temperature": {
769+
"value": 25.0
770+
}
771+
}
772+
}
773+
}
774+
}
775+
},
776+
"patchConditions": {
777+
"thing:/features/sensor": "not(exists(/features/sensor))"
778+
},
779+
"initializeMissingPropertiesFromDefaults": true
780+
}
781+
```
782+
tags:
783+
- Things
784+
parameters:
785+
- $ref: '#/components/parameters/ThingIdPathParam'
786+
- name: dry-run
787+
in: query
788+
description: 'If set to `true`, performs a dry-run and returns the migration result without applying changes.'
789+
required: false
790+
schema:
791+
type: boolean
792+
default: false
793+
requestBody:
794+
description: 'JSON payload containing the new definition URL, migration payload, patch conditions, and initialization flag.'
795+
required: true
796+
content:
797+
application/json:
798+
schema:
799+
$ref: '#/components/requestBodies/MigrateThingDefinitionRequest/content/application~1json/schema'
800+
responses:
801+
'200':
802+
description: 'The thing definition was successfully updated, and the updated Thing is returned.'
803+
content:
804+
application/json:
805+
schema:
806+
$ref: '#/components/responses/MigrateThingDefinitionResponse/content/application~1json/schema'
807+
'202':
808+
description: Dry-run successful. The migration result is returned without applying changes.
809+
content:
810+
application/json:
811+
schema:
812+
$ref: '#/components/responses/MigrateThingDefinitionResponse/content/application~1json/schema'
813+
'400':
814+
description: The request could not be processed due to invalid input.
815+
content:
816+
application/json:
817+
schema:
818+
$ref: '#/components/schemas/AdvancedError'
819+
'401':
820+
description: Unauthorized request due to missing authentication.
821+
content:
822+
application/json:
823+
schema:
824+
$ref: '#/components/schemas/AdvancedError'
825+
'404':
826+
description: The specified thing could not be found.
827+
content:
828+
application/json:
829+
schema:
830+
$ref: '#/components/schemas/AdvancedError'
831+
'412':
832+
description: The update conditions were not met.
833+
content:
834+
application/json:
835+
schema:
836+
$ref: '#/components/schemas/AdvancedError'
742837
'/api/2/things/{thingId}/definition':
743838
get:
744839
summary: Retrieve the definition of a specific thing
@@ -8212,6 +8307,59 @@ components:
82128307
randomizationInterval: 5m
82138308
description: Optional request payload for `activateTokenIntegration` policy action.
82148309
required: false
8310+
MigrateThingDefinitionRequest:
8311+
content:
8312+
application/json:
8313+
schema:
8314+
type: object
8315+
description: JSON payload to migrate the definition of a Thing.
8316+
properties:
8317+
thingDefinitionUrl:
8318+
type: string
8319+
format: uri
8320+
description: The URL of the new Thing definition to be applied.
8321+
example: 'https://models.example.com/thing-definition-1.0.0.tm.jsonld'
8322+
migrationPayload:
8323+
type: object
8324+
description: Optional migration payload with updates to attributes and features.
8325+
properties:
8326+
attributes:
8327+
type: object
8328+
additionalProperties: true
8329+
description: Attributes to be updated in the thing.
8330+
example:
8331+
manufacturer: New Corp
8332+
location: 'Berlin, main floor'
8333+
features:
8334+
type: object
8335+
additionalProperties:
8336+
type: object
8337+
properties:
8338+
properties:
8339+
type: object
8340+
additionalProperties: true
8341+
description: Features to be updated in the thing.
8342+
example:
8343+
thermostat:
8344+
properties:
8345+
status:
8346+
temperature:
8347+
value: 23.5
8348+
unit: DEGREE_CELSIUS
8349+
patchConditions:
8350+
type: object
8351+
description: Optional conditions to apply the migration only if the existing thing matches the specified values.
8352+
additionalProperties:
8353+
type: string
8354+
example:
8355+
'thing:/features/thermostat': not(exists(/features/thermostat))
8356+
initializeMissingPropertiesFromDefaults:
8357+
type: boolean
8358+
description: Flag indicating whether missing properties should be initialized with default values.
8359+
example: true
8360+
default: false
8361+
required:
8362+
- thingDefinitionUrl
82158363
responses:
82168364
EntityTooLarge:
82178365
description: The created or modified entity is larger than the accepted limit of 100 kB.
@@ -8300,6 +8448,41 @@ components:
83008448
application/json:
83018449
schema:
83028450
$ref: '#/components/schemas/ModuleUpdatedLogLevel'
8451+
MigrateThingDefinitionResponse:
8452+
description: 'The thing definition was successfully updated, and the updated Thing is returned.'
8453+
content:
8454+
application/json:
8455+
schema:
8456+
type: object
8457+
description: Response payload after applying or simulating a migration to a Thing.
8458+
properties:
8459+
thingId:
8460+
type: string
8461+
description: Unique identifier representing the migrated Thing.
8462+
patch:
8463+
type: object
8464+
description: The patch containing updates to the Thing.
8465+
properties:
8466+
definition:
8467+
$ref: '#/components/schemas/Definition'
8468+
attributes:
8469+
$ref: '#/components/schemas/Attributes'
8470+
features:
8471+
$ref: '#/components/schemas/Features'
8472+
mergeStatus:
8473+
type: string
8474+
description: |
8475+
Indicates the result of the migration process.
8476+
- `APPLIED`: The migration was successfully applied.
8477+
- `DRY_RUN`: The migration result was calculated but not applied.
8478+
enum:
8479+
- APPLIED
8480+
- DRY_RUN
8481+
example: APPLIED
8482+
required:
8483+
- thingId
8484+
- patch
8485+
- mergeStatus
83038486
parameters:
83048487
AllowPolicyLockoutParam:
83058488
name: allow-policy-lockout

documentation/src/main/resources/openapi/sources/api-2-index.yml

+13
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ paths:
6060
$ref: "./paths/things/index.yml"
6161
'/api/2/things/{thingId}':
6262
$ref: "./paths/things/thing.yml"
63+
'/api/2/things/{thingId}/migrateDefinition':
64+
$ref: "./paths/things/migrateDefinition.yml"
6365
'/api/2/things/{thingId}/definition':
6466
$ref: "./paths/things/definition.yml"
6567
'/api/2/things/{thingId}/policyId':
@@ -214,6 +216,11 @@ components:
214216
$ref: "./requests/patchValue.yml"
215217
ActivateTokenIntegration:
216218
$ref: "./requests/policies/actions/activateTokenIntegration.yml"
219+
MigrateThingDefinitionRequest:
220+
content:
221+
application/json:
222+
schema:
223+
$ref: "./requests/things/migrateThingDefinitionRequest.yml"
217224

218225
responses:
219226
EntityTooLarge:
@@ -230,6 +237,12 @@ components:
230237
$ref: "./responses/successUpdateLogLevel.yml"
231238
SuccessUpdateLogLevelSinglePod:
232239
$ref: "./responses/successUpdateLogLevelSinglePod.yml"
240+
MigrateThingDefinitionResponse:
241+
description: The thing definition was successfully updated, and the updated Thing is returned.
242+
content:
243+
application/json:
244+
schema:
245+
$ref: "./responses/things/migrateThingDefinitionResponse.yml"
233246

234247
parameters:
235248
AllowPolicyLockoutParam:

0 commit comments

Comments
 (0)