diff --git a/things/service/src/main/java/org/eclipse/ditto/things/service/enforcement/ThingEnforcerActor.java b/things/service/src/main/java/org/eclipse/ditto/things/service/enforcement/ThingEnforcerActor.java index 78cc9aefb7..a494d74344 100644 --- a/things/service/src/main/java/org/eclipse/ditto/things/service/enforcement/ThingEnforcerActor.java +++ b/things/service/src/main/java/org/eclipse/ditto/things/service/enforcement/ThingEnforcerActor.java @@ -19,6 +19,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import java.util.function.Supplier; import javax.annotation.Nullable; @@ -577,31 +578,30 @@ private CompletionStage doesThingExist() { ((MessageCommand) messageCommand) .getMessage(); - if (message.getPayload().isPresent() && !isJsonMessageContent(message)) { - return CompletableFuture.failedFuture( - WotThingModelPayloadValidationException + // lazily only supply JsonValue if validation is enabled for the message: + final Supplier messageCommandPayloadSupplier = () -> { + if (message.getPayload().isPresent() && !isJsonMessageContent(message)) { + throw WotThingModelPayloadValidationException .newBuilder("Could not validate non-JSON message content type <" + message.getContentType().orElse("?") + "> for message subject " + "<" + message.getSubject() + ">" ) .dittoHeaders(messageCommand.getDittoHeaders()) - .build() - ); - } + .build(); + } - final MessageDirection messageDirection = message.getDirection(); - final JsonValue messageCommandPayload = message - .getPayload() - .orElse(null); + return message.getPayload().orElse(null); + }; + final MessageDirection messageDirection = message.getDirection(); if (messageCommand instanceof SendThingMessage sendThingMessage) { return performWotBasedThingMessageValidation(messageCommand, sendThingMessage, messageDirection, - messageCommandPayload + messageCommandPayloadSupplier ).thenApply(aVoid -> messageCommand); } else if (messageCommand instanceof SendFeatureMessage sendFeatureMessage) { final String featureId = sendFeatureMessage.getFeatureId(); return performWotBasedFeatureMessageValidation(messageCommand, sendFeatureMessage, featureId, - messageDirection, messageCommandPayload + messageDirection, messageCommandPayloadSupplier ).thenApply(aVoid -> messageCommand); } else { @@ -612,7 +612,7 @@ private CompletionStage doesThingExist() { private CompletionStage performWotBasedThingMessageValidation(final MessageCommand messageCommand, final SendThingMessage sendThingMessage, final MessageDirection messageDirection, - @Nullable final JsonValue messageCommandPayload + final Supplier messageCommandPayloadSupplier ) { return resolveThingDefinition() .thenCompose(optThingDefinition -> { @@ -620,7 +620,7 @@ private CompletionStage performWotBasedThingMessageValidation(final Messag return thingModelValidator.validateThingActionInput( optThingDefinition.orElse(null), sendThingMessage.getMessage().getSubject(), - messageCommandPayload, + messageCommandPayloadSupplier, sendThingMessage.getResourcePath(), sendThingMessage.getDittoHeaders() ); @@ -628,7 +628,7 @@ private CompletionStage performWotBasedThingMessageValidation(final Messag return thingModelValidator.validateThingEventData( optThingDefinition.orElse(null), sendThingMessage.getMessage().getSubject(), - messageCommandPayload, + messageCommandPayloadSupplier, sendThingMessage.getResourcePath(), sendThingMessage.getDittoHeaders() ); @@ -646,7 +646,7 @@ private CompletionStage performWotBasedFeatureMessageValidation(final Mess final SendFeatureMessage sendFeatureMessage, final String featureId, final MessageDirection messageDirection, - @Nullable final JsonValue messageCommandPayload + final Supplier messageCommandPayloadSupplier ) { return resolveThingAndFeatureDefinition(featureId) .thenCompose(optDefinitionPair -> { @@ -656,7 +656,7 @@ private CompletionStage performWotBasedFeatureMessageValidation(final Mess optDefinitionPair.second().orElse(null), featureId, sendFeatureMessage.getMessage().getSubject(), - messageCommandPayload, + messageCommandPayloadSupplier, sendFeatureMessage.getResourcePath(), sendFeatureMessage.getDittoHeaders() ); @@ -666,7 +666,7 @@ private CompletionStage performWotBasedFeatureMessageValidation(final Mess optDefinitionPair.second().orElse(null), featureId, sendFeatureMessage.getMessage().getSubject(), - messageCommandPayload, + messageCommandPayloadSupplier, sendFeatureMessage.getResourcePath(), sendFeatureMessage.getDittoHeaders() ); @@ -699,18 +699,29 @@ private CompletionStage performWotBasedFeatureMessageValidation(final Mess ); } - final MessageDirection messageDirection = message.getDirection(); - final JsonValue messageCommandPayload = message - .getPayload() - .orElse(null); + // lazily only supply JsonValue if validation is enabled for the message: + final Supplier messageCommandPayloadSupplier = () -> { + if (message.getPayload().isPresent() && !isJsonMessageContent(message)) { + throw WotThingModelPayloadValidationException + .newBuilder("Could not validate non-JSON message content type <" + + message.getContentType().orElse("?") + "> for message response subject " + + "<" + message.getSubject() + ">" + ) + .dittoHeaders(messageCommandResponse.getDittoHeaders()) + .build(); + } + + return message.getPayload().orElse(null); + }; + final MessageDirection messageDirection = message.getDirection(); if (messageDirection == MessageDirection.TO && messageCommandResponse instanceof SendThingMessageResponse sendThingMessageResponse) { return resolveThingDefinition() .thenCompose(optThingDefinition -> thingModelValidator.validateThingActionOutput( optThingDefinition.orElse(null), sendThingMessageResponse.getMessage().getSubject(), - messageCommandPayload, + messageCommandPayloadSupplier, sendThingMessageResponse.getResourcePath(), sendThingMessageResponse.getDittoHeaders() )) @@ -724,7 +735,7 @@ private CompletionStage performWotBasedFeatureMessageValidation(final Mess optDefinitionPair.second().orElse(null), featureId, sendFeatureMessageResponse.getMessage().getSubject(), - messageCommandPayload, + messageCommandPayloadSupplier, sendFeatureMessageResponse.getResourcePath(), sendFeatureMessageResponse.getDittoHeaders() )) diff --git a/things/service/src/test/java/org/eclipse/ditto/things/service/enforcement/LiveSignalEnforcementTest.java b/things/service/src/test/java/org/eclipse/ditto/things/service/enforcement/LiveSignalEnforcementTest.java index fa8fbf50f3..c0662c11bf 100644 --- a/things/service/src/test/java/org/eclipse/ditto/things/service/enforcement/LiveSignalEnforcementTest.java +++ b/things/service/src/test/java/org/eclipse/ditto/things/service/enforcement/LiveSignalEnforcementTest.java @@ -305,6 +305,7 @@ public void correlationIdDifferentInCaseOfConflict() { supervisor.tell(message, getRef()); + expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); final var firstPublishRead = expectPubsubMessagePublish(message.getEntityId()); @@ -315,6 +316,7 @@ public void correlationIdDifferentInCaseOfConflict() { supervisor.tell(message, getRef()); + expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); final var secondPublishRead = expectPubsubMessagePublish(message.getEntityId()); @@ -351,6 +353,7 @@ public void acceptMessageCommandByPolicy() { final MessageCommand msgCommand = thingMessageCommand("abc"); supervisor.tell(msgCommand, getRef()); + expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); expectPubsubMessagePublish(msgCommand.getEntityId()); @@ -378,6 +381,7 @@ public void acceptFeatureMessageCommandByPolicy() { final MessageCommand msgCommand = featureMessageCommand(); supervisor.tell(msgCommand, getRef()); + expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); expectAndAnswerSudoRetrieveThing(sudoRetrieveThingResponse); expectPubsubMessagePublish(msgCommand.getEntityId()); diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java index debea34997..3a736a7b25 100644 --- a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/DefaultWotThingModelValidator.java @@ -24,6 +24,7 @@ import java.util.concurrent.Executor; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -231,7 +232,7 @@ public CompletionStage validateThingScopedDeletion(@Nullable final ThingDe @Override public CompletionStage validateThingActionInput(@Nullable final ThingDefinition thingDefinition, final String messageSubject, - @Nullable final JsonValue inputPayload, + final Supplier inputPayloadSupplier, final JsonPointer resourcePath, final DittoHeaders dittoHeaders ) { @@ -240,7 +241,7 @@ public CompletionStage validateThingActionInput(@Nullable final ThingDefin .map(validationConfig -> fetchResolveAndValidateWith(thingDefinition, dittoHeaders, thingModel -> selectValidation(validationConfig) .validateThingActionInput(thingModel, - messageSubject, inputPayload, resourcePath, context + messageSubject, inputPayloadSupplier.get(), resourcePath, context ).handle(applyLogingErrorOnlyStrategy(validationConfig, context, "validateThingActionInput")) )) .orElseGet(DefaultWotThingModelValidator::success); @@ -249,7 +250,7 @@ public CompletionStage validateThingActionInput(@Nullable final ThingDefin @Override public CompletionStage validateThingActionOutput(@Nullable final ThingDefinition thingDefinition, final String messageSubject, - @Nullable final JsonValue outputPayload, + final Supplier outputPayloadSupplier, final JsonPointer resourcePath, final DittoHeaders dittoHeaders ) { @@ -258,7 +259,7 @@ public CompletionStage validateThingActionOutput(@Nullable final ThingDefi .map(validationConfig -> fetchResolveAndValidateWith(thingDefinition, dittoHeaders, thingModel -> selectValidation(validationConfig) .validateThingActionOutput(thingModel, - messageSubject, outputPayload, resourcePath, context + messageSubject, outputPayloadSupplier.get(), resourcePath, context ).handle(applyLogingErrorOnlyStrategy(validationConfig, context, "validateThingActionOutput")) )) .orElseGet(DefaultWotThingModelValidator::success); @@ -267,7 +268,7 @@ public CompletionStage validateThingActionOutput(@Nullable final ThingDefi @Override public CompletionStage validateThingEventData(@Nullable final ThingDefinition thingDefinition, final String messageSubject, - @Nullable final JsonValue dataPayload, + final Supplier dataPayloadSupplier, final JsonPointer resourcePath, final DittoHeaders dittoHeaders ) { @@ -276,7 +277,7 @@ public CompletionStage validateThingEventData(@Nullable final ThingDefinit .map(validationConfig -> fetchResolveAndValidateWith(thingDefinition, dittoHeaders, thingModel -> selectValidation(validationConfig) .validateThingEventData(thingModel, - messageSubject, dataPayload, resourcePath, context + messageSubject, dataPayloadSupplier.get(), resourcePath, context ).handle(applyLogingErrorOnlyStrategy(validationConfig, context, "validateThingEventData")) )) .orElseGet(DefaultWotThingModelValidator::success); @@ -493,7 +494,7 @@ public CompletionStage validateFeatureActionInput(@Nullable final ThingDef @Nullable final FeatureDefinition featureDefinition, final String featureId, final String messageSubject, - @Nullable final JsonValue inputPayload, + final Supplier inputPayloadSupplier, final JsonPointer resourcePath, final DittoHeaders dittoHeaders ) { @@ -504,7 +505,7 @@ public CompletionStage validateFeatureActionInput(@Nullable final ThingDef dittoHeaders, featureThingModel -> selectValidation(validationConfig) .validateFeatureActionInput(featureThingModel, - featureId, messageSubject, inputPayload, resourcePath, context + featureId, messageSubject, inputPayloadSupplier.get(), resourcePath, context ).handle(applyLogingErrorOnlyStrategy(validationConfig, context, "validateFeatureActionInput")) )) .orElseGet(DefaultWotThingModelValidator::success); @@ -515,7 +516,7 @@ public CompletionStage validateFeatureActionOutput(@Nullable final ThingDe @Nullable final FeatureDefinition featureDefinition, final String featureId, final String messageSubject, - @Nullable final JsonValue inputPayload, + final Supplier inputPayloadSupplier, final JsonPointer resourcePath, final DittoHeaders dittoHeaders ) { @@ -526,7 +527,7 @@ public CompletionStage validateFeatureActionOutput(@Nullable final ThingDe dittoHeaders, featureThingModel -> selectValidation(validationConfig) .validateFeatureActionOutput(featureThingModel, - featureId, messageSubject, inputPayload, resourcePath, context + featureId, messageSubject, inputPayloadSupplier.get(), resourcePath, context ).handle(applyLogingErrorOnlyStrategy(validationConfig, context, "validateFeatureActionOutput")) )) .orElseGet(DefaultWotThingModelValidator::success); @@ -537,7 +538,7 @@ public CompletionStage validateFeatureEventData(@Nullable final ThingDefin @Nullable final FeatureDefinition featureDefinition, final String featureId, final String messageSubject, - @Nullable final JsonValue dataPayload, + final Supplier dataPayloadSupplier, final JsonPointer resourcePath, final DittoHeaders dittoHeaders ) { @@ -548,7 +549,7 @@ public CompletionStage validateFeatureEventData(@Nullable final ThingDefin dittoHeaders, featureThingModel -> selectValidation(validationConfig) .validateFeatureEventData(featureThingModel, - featureId, messageSubject, dataPayload, resourcePath, context + featureId, messageSubject, dataPayloadSupplier.get(), resourcePath, context ).handle(applyLogingErrorOnlyStrategy(validationConfig, context, "validateFeatureEventData")) )) .orElseGet(DefaultWotThingModelValidator::success); diff --git a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java index a7aa8bfb8a..a131225212 100644 --- a/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java +++ b/wot/api/src/main/java/org/eclipse/ditto/wot/api/validator/WotThingModelValidator.java @@ -14,6 +14,7 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; +import java.util.function.Supplier; import javax.annotation.Nullable; @@ -197,7 +198,7 @@ CompletionStage validateThingScopedDeletion(@Nullable ThingDefinition thin * * @param thingDefinition the ThingDefinition to retrieve the WoT TM from * @param messageSubject the (Thing) message subject - * @param inputPayload the input payload to validate + * @param inputPayloadSupplier the supplier of the input payload to validate * @param resourcePath the originating path of the command which caused validation * @param dittoHeaders the DittoHeaders to use in order to build a potential exception * @return a CompletionStage finished successfully with {@code null} or finished exceptionally in case of a @@ -205,7 +206,7 @@ CompletionStage validateThingScopedDeletion(@Nullable ThingDefinition thin */ CompletionStage validateThingActionInput(@Nullable ThingDefinition thingDefinition, String messageSubject, - @Nullable JsonValue inputPayload, + Supplier inputPayloadSupplier, JsonPointer resourcePath, DittoHeaders dittoHeaders ); @@ -216,7 +217,7 @@ CompletionStage validateThingActionInput(@Nullable ThingDefinition thingDe * * @param thingDefinition the ThingDefinition to retrieve the WoT TM from * @param messageSubject the (Thing) message subject - * @param outputPayload the output payload to validate + * @param outputPayloadSupplier the supplier of the output payload to validate * @param resourcePath the originating path of the command which caused validation * @param dittoHeaders the DittoHeaders to use in order to build a potential exception * @return a CompletionStage finished successfully with {@code null} or finished exceptionally in case of a @@ -224,7 +225,7 @@ CompletionStage validateThingActionInput(@Nullable ThingDefinition thingDe */ CompletionStage validateThingActionOutput(@Nullable ThingDefinition thingDefinition, String messageSubject, - @Nullable JsonValue outputPayload, + Supplier outputPayloadSupplier, JsonPointer resourcePath, DittoHeaders dittoHeaders ); @@ -235,7 +236,7 @@ CompletionStage validateThingActionOutput(@Nullable ThingDefinition thingD * * @param thingDefinition the ThingDefinition to retrieve the WoT TM from * @param messageSubject the (Thing) message subject - * @param dataPayload the data payload to validate + * @param dataPayloadSupplier the supplier of data payload to validate * @param resourcePath the originating path of the command which caused validation * @param dittoHeaders the DittoHeaders to use in order to build a potential exception * @return a CompletionStage finished successfully with {@code null} or finished exceptionally in case of a @@ -243,7 +244,7 @@ CompletionStage validateThingActionOutput(@Nullable ThingDefinition thingD */ CompletionStage validateThingEventData(@Nullable ThingDefinition thingDefinition, String messageSubject, - @Nullable JsonValue dataPayload, + Supplier dataPayloadSupplier, JsonPointer resourcePath, DittoHeaders dittoHeaders ); @@ -459,7 +460,7 @@ CompletionStage validateFeatureScopedDeletion(@Nullable ThingDefinition th * @param featureDefinition the FeatureDefinition to retrieve the WoT TM from * @param featureId the ID of the feature to validate the message input payload for * @param messageSubject the (Feature) message subject - * @param inputPayload the input payload to validate + * @param inputPayloadSupplier the supplier of input payload to validate * @param resourcePath the originating path of the command which caused validation * @param dittoHeaders the DittoHeaders to use in order to build a potential exception * @return a CompletionStage finished successfully with {@code null} or finished exceptionally in case of a @@ -469,7 +470,7 @@ CompletionStage validateFeatureActionInput(@Nullable ThingDefinition thing @Nullable FeatureDefinition featureDefinition, String featureId, String messageSubject, - @Nullable JsonValue inputPayload, + Supplier inputPayloadSupplier, JsonPointer resourcePath, DittoHeaders dittoHeaders ); @@ -482,7 +483,7 @@ CompletionStage validateFeatureActionInput(@Nullable ThingDefinition thing * @param featureDefinition the FeatureDefinition to retrieve the WoT TM from * @param featureId the ID of the feature to validate the message output payload for * @param messageSubject the (Feature) message subject - * @param outputPayload the output payload to validate + * @param outputPayloadSupplier the supplier of output payload to validate * @param resourcePath the originating path of the command which caused validation * @param dittoHeaders the DittoHeaders to use in order to build a potential exception * @return a CompletionStage finished successfully with {@code null} or finished exceptionally in case of a @@ -492,7 +493,7 @@ CompletionStage validateFeatureActionOutput(@Nullable ThingDefinition thin @Nullable FeatureDefinition featureDefinition, String featureId, String messageSubject, - @Nullable JsonValue outputPayload, + Supplier outputPayloadSupplier, JsonPointer resourcePath, DittoHeaders dittoHeaders ); @@ -505,7 +506,7 @@ CompletionStage validateFeatureActionOutput(@Nullable ThingDefinition thin * @param featureDefinition the FeatureDefinition to retrieve the WoT TM from * @param featureId the ID of the feature to validate the message payload for * @param messageSubject the (Feature) message subject - * @param dataPayload the data payload to validate + * @param dataPayloadSupplier the supplier of data payload to validate * @param resourcePath the originating path of the command which caused validation * @param dittoHeaders the DittoHeaders to use in order to build a potential exception * @return a CompletionStage finished successfully with {@code null} or finished exceptionally in case of a @@ -515,7 +516,7 @@ CompletionStage validateFeatureEventData(@Nullable ThingDefinition thingDe @Nullable FeatureDefinition featureDefinition, String featureId, String messageSubject, - @Nullable JsonValue dataPayload, + Supplier dataPayloadSupplier, JsonPointer resourcePath, DittoHeaders dittoHeaders );