From a3ea1f6f10ab2cbcd9f463a1bf9727c0e4e4add2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Tue, 19 Dec 2023 15:35:16 +0100 Subject: [PATCH 1/7] remove noisy INFO log statement in StreamingSessionActor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Jäckle --- .../actors/StreamingSessionActor.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/streaming/actors/StreamingSessionActor.java b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/streaming/actors/StreamingSessionActor.java index 942f470f78..7e9c307366 100755 --- a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/streaming/actors/StreamingSessionActor.java +++ b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/streaming/actors/StreamingSessionActor.java @@ -24,6 +24,21 @@ import javax.annotation.Nullable; +import org.apache.pekko.Done; +import org.apache.pekko.actor.AbstractActorWithTimers; +import org.apache.pekko.actor.ActorRef; +import org.apache.pekko.actor.Cancellable; +import org.apache.pekko.actor.CoordinatedShutdown; +import org.apache.pekko.actor.Props; +import org.apache.pekko.actor.Terminated; +import org.apache.pekko.japi.pf.PFBuilder; +import org.apache.pekko.japi.pf.ReceiveBuilder; +import org.apache.pekko.pattern.Patterns; +import org.apache.pekko.stream.KillSwitch; +import org.apache.pekko.stream.SourceRef; +import org.apache.pekko.stream.javadsl.Keep; +import org.apache.pekko.stream.javadsl.Sink; +import org.apache.pekko.stream.javadsl.SourceQueueWithComplete; import org.eclipse.ditto.base.model.acks.AcknowledgementLabel; import org.eclipse.ditto.base.model.acks.AcknowledgementLabelNotDeclaredException; import org.eclipse.ditto.base.model.acks.AcknowledgementLabelNotUniqueException; @@ -87,21 +102,6 @@ import org.eclipse.ditto.thingsearch.model.signals.commands.ThingSearchCommand; import org.eclipse.ditto.thingsearch.model.signals.events.SubscriptionEvent; -import org.apache.pekko.Done; -import org.apache.pekko.actor.AbstractActorWithTimers; -import org.apache.pekko.actor.ActorRef; -import org.apache.pekko.actor.Cancellable; -import org.apache.pekko.actor.CoordinatedShutdown; -import org.apache.pekko.actor.Props; -import org.apache.pekko.actor.Terminated; -import org.apache.pekko.japi.pf.PFBuilder; -import org.apache.pekko.japi.pf.ReceiveBuilder; -import org.apache.pekko.pattern.Patterns; -import org.apache.pekko.stream.KillSwitch; -import org.apache.pekko.stream.SourceRef; -import org.apache.pekko.stream.javadsl.Keep; -import org.apache.pekko.stream.javadsl.Sink; -import org.apache.pekko.stream.javadsl.SourceQueueWithComplete; import scala.PartialFunction; /** @@ -314,7 +314,6 @@ private Receive createOutgoingSignalBehavior() { @Nullable final var session = streamingSessions.get(streamingType); if (null != session && isSessionAllowedToReceiveSignal(signal, session, streamingType)) { final ThreadSafeDittoLoggingAdapter l = logger.withCorrelationId(signal); - l.info("Publishing Signal of type <{}> in <{}> session", signal.getType(), type); l.debug("Publishing Signal of type <{}> in <{}> session: {}", type, signal.getType(), signal); final DittoHeaders sessionHeaders = DittoHeaders.newBuilder() From 43cad4f5ea41d63c8f7a372e87ac3ee7de11969b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Mon, 18 Dec 2023 19:49:48 +0100 Subject: [PATCH 2/7] #1844 fix resolving revision or timestamp from the future was not directly denied MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Jäckle --- ...nnectionHistoryNotAccessibleException.java | 2 +- .../AbstractPersistenceActor.java | 48 +++++++++++++------ .../PolicyHistoryNotAccessibleException.java | 2 +- .../ThingHistoryNotAccessibleException.java | 2 +- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/commands/exceptions/ConnectionHistoryNotAccessibleException.java b/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/commands/exceptions/ConnectionHistoryNotAccessibleException.java index e51a650b3a..b3a59c8be1 100755 --- a/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/commands/exceptions/ConnectionHistoryNotAccessibleException.java +++ b/connectivity/model/src/main/java/org/eclipse/ditto/connectivity/model/signals/commands/exceptions/ConnectionHistoryNotAccessibleException.java @@ -57,7 +57,7 @@ public final class ConnectionHistoryNotAccessibleException extends DittoRuntimeE private static final String DEFAULT_DESCRIPTION = "Check if the ID of your requested Connection was correct, you have sufficient permissions and ensure that the " + - "asked for revision/timestamp does not exceed the history-retention-duration."; + "asked for revision/timestamp does not exceed the history-retention-duration or is from the future."; private static final long serialVersionUID = -998877665544332221L; diff --git a/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java b/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java index ef1e65210e..a335bbb873 100755 --- a/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java +++ b/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java @@ -23,6 +23,19 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.apache.pekko.actor.ActorRef; +import org.apache.pekko.actor.Cancellable; +import org.apache.pekko.japi.pf.ReceiveBuilder; +import org.apache.pekko.pattern.Patterns; +import org.apache.pekko.persistence.RecoveryCompleted; +import org.apache.pekko.persistence.RecoveryTimedOut; +import org.apache.pekko.persistence.SaveSnapshotFailure; +import org.apache.pekko.persistence.SaveSnapshotSuccess; +import org.apache.pekko.persistence.SnapshotOffer; +import org.apache.pekko.persistence.SnapshotProtocol; +import org.apache.pekko.persistence.SnapshotSelectionCriteria; +import org.apache.pekko.persistence.query.EventEnvelope; +import org.apache.pekko.stream.javadsl.Sink; import org.bson.BsonDocument; import org.eclipse.ditto.base.api.commands.sudo.SudoCommand; import org.eclipse.ditto.base.model.entity.id.EntityId; @@ -40,11 +53,11 @@ import org.eclipse.ditto.base.model.signals.commands.Command; import org.eclipse.ditto.base.model.signals.events.EventsourcedEvent; import org.eclipse.ditto.base.model.signals.events.GlobalEventRegistry; +import org.eclipse.ditto.internal.utils.config.ScopedConfig; +import org.eclipse.ditto.internal.utils.namespaces.BlockedNamespaces; import org.eclipse.ditto.internal.utils.pekko.PingCommand; import org.eclipse.ditto.internal.utils.pekko.PingCommandResponse; import org.eclipse.ditto.internal.utils.pekko.logging.DittoDiagnosticLoggingAdapter; -import org.eclipse.ditto.internal.utils.config.ScopedConfig; -import org.eclipse.ditto.internal.utils.namespaces.BlockedNamespaces; import org.eclipse.ditto.internal.utils.persistence.SnapshotAdapter; import org.eclipse.ditto.internal.utils.persistence.mongo.AbstractMongoEventAdapter; import org.eclipse.ditto.internal.utils.persistence.mongo.DittoBsonJson; @@ -64,19 +77,6 @@ import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.json.JsonValue; -import org.apache.pekko.actor.ActorRef; -import org.apache.pekko.actor.Cancellable; -import org.apache.pekko.japi.pf.ReceiveBuilder; -import org.apache.pekko.pattern.Patterns; -import org.apache.pekko.persistence.RecoveryCompleted; -import org.apache.pekko.persistence.RecoveryTimedOut; -import org.apache.pekko.persistence.SaveSnapshotFailure; -import org.apache.pekko.persistence.SaveSnapshotSuccess; -import org.apache.pekko.persistence.SnapshotOffer; -import org.apache.pekko.persistence.SnapshotProtocol; -import org.apache.pekko.persistence.SnapshotSelectionCriteria; -import org.apache.pekko.persistence.query.EventEnvelope; -import org.apache.pekko.stream.javadsl.Sink; import scala.Option; /** @@ -377,10 +377,28 @@ private void handleHistoricalRetrieveCommand(final C command) { .ofNullable(command.getDittoHeaders().get(DittoHeaderDefinition.AT_HISTORICAL_REVISION.getKey())) .map(Long::parseLong) .orElseGet(this::lastSequenceNr); + if (atHistoricalRevision > lastSequenceNr()) { + getSender().tell( + newHistoryNotAccessibleExceptionBuilder(atHistoricalRevision) + .dittoHeaders(command.getDittoHeaders()) + .build(), + getSelf() + ); + return; + } final Instant atHistoricalTimestamp = Optional .ofNullable(command.getDittoHeaders().get(DittoHeaderDefinition.AT_HISTORICAL_TIMESTAMP.getKey())) .map(Instant::parse) .orElse(Instant.EPOCH); + if (atHistoricalTimestamp.isAfter(Instant.now())) { + getSender().tell( + newHistoryNotAccessibleExceptionBuilder(atHistoricalTimestamp) + .dittoHeaders(command.getDittoHeaders()) + .build(), + getSelf() + ); + return; + } loadSnapshot(persistenceId(), SnapshotSelectionCriteria.create( atHistoricalRevision, diff --git a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyHistoryNotAccessibleException.java b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyHistoryNotAccessibleException.java index 51401af371..8bcf38ea33 100755 --- a/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyHistoryNotAccessibleException.java +++ b/policies/model/src/main/java/org/eclipse/ditto/policies/model/signals/commands/exceptions/PolicyHistoryNotAccessibleException.java @@ -56,7 +56,7 @@ public final class PolicyHistoryNotAccessibleException extends DittoRuntimeExcep private static final String DEFAULT_DESCRIPTION = "Check if the ID of your requested Policy was correct, you have sufficient permissions and ensure that the " + - "asked for revision/timestamp does not exceed the history-retention-duration."; + "asked for revision/timestamp does not exceed the history-retention-duration or is from the future."; private static final long serialVersionUID = 4242422323239998882L; diff --git a/things/model/src/main/java/org/eclipse/ditto/things/model/signals/commands/exceptions/ThingHistoryNotAccessibleException.java b/things/model/src/main/java/org/eclipse/ditto/things/model/signals/commands/exceptions/ThingHistoryNotAccessibleException.java index e06eb9e1a1..7c799cb018 100755 --- a/things/model/src/main/java/org/eclipse/ditto/things/model/signals/commands/exceptions/ThingHistoryNotAccessibleException.java +++ b/things/model/src/main/java/org/eclipse/ditto/things/model/signals/commands/exceptions/ThingHistoryNotAccessibleException.java @@ -56,7 +56,7 @@ public final class ThingHistoryNotAccessibleException extends DittoRuntimeExcept private static final String DEFAULT_DESCRIPTION = "Check if the ID of your requested Thing was correct, you have sufficient permissions and ensure that the " + - "asked for revision/timestamp does not exceed the history-retention-duration."; + "asked for revision/timestamp does not exceed the history-retention-duration or is from the future."; private static final long serialVersionUID = 8883736111094383234L; From 6f82f79fa9e58765b6cd02cab1c6a5f805576ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Mon, 18 Dec 2023 20:07:22 +0100 Subject: [PATCH 3/7] #1844 fix wrong "reduction" when calculating retrieving `at-historical-revision` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Jäckle --- .../utils/persistentactors/AbstractPersistenceActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java b/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java index a335bbb873..680027594c 100755 --- a/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java +++ b/internal/utils/persistent-actors/src/main/java/org/eclipse/ditto/internal/utils/persistentactors/AbstractPersistenceActor.java @@ -484,7 +484,7 @@ private void historicalRetrieveHandleLoadSnapshotResult(final C command, } }) .reduce((ewe1, ewe2) -> new EntityWithEvent( - eventStrategy.handle(ewe2.event, ewe2.entity, ewe2.revision), + eventStrategy.handle(ewe2.event, ewe1.entity, ewe2.revision), ewe2.event )) .runWith(Sink.foreach(entityWithEvent -> From 6337324e7e26e1aa591397cfda44768b0ab9f1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Thu, 21 Dec 2023 18:06:10 +0100 Subject: [PATCH 4/7] enhanced documentation, adding the "condition" for POST search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Jäckle --- .../main/resources/openapi/ditto-api-2.yml | 9 +++++++++ .../openapi/sources/paths/search/things.yml | 2 ++ .../properties/searchConditionProperty.yml | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 documentation/src/main/resources/openapi/sources/schemas/properties/searchConditionProperty.yml diff --git a/documentation/src/main/resources/openapi/ditto-api-2.yml b/documentation/src/main/resources/openapi/ditto-api-2.yml index be48b3cb0b..65b78df210 100644 --- a/documentation/src/main/resources/openapi/ditto-api-2.yml +++ b/documentation/src/main/resources/openapi/ditto-api-2.yml @@ -6907,6 +6907,15 @@ paths: The deprecated paging option `limit` may not be combined with the other paging options `size` and `cursor`. type: string + condition: + description: |- + Similar to the `filter`, a `condition` may be passed to ensure strong consistency when querying things. + + This `condition` has the same syntax and semantics than the `filter` - it is however applied on the matched things + selected by the `filter` - on their current state. + + So combining this together with `filter` can provide strong consistency when performing a search. + type: string encoding: filter: style: form diff --git a/documentation/src/main/resources/openapi/sources/paths/search/things.yml b/documentation/src/main/resources/openapi/sources/paths/search/things.yml index 9d14961542..47d2f4b86d 100644 --- a/documentation/src/main/resources/openapi/sources/paths/search/things.yml +++ b/documentation/src/main/resources/openapi/sources/paths/search/things.yml @@ -173,6 +173,8 @@ post: $ref: '../../schemas/properties/thingFieldsProperty.yml' option: $ref: '../../schemas/properties/optionProperty.yml' + condition: + $ref: '../../schemas/properties/searchConditionProperty.yml' encoding: filter: style: form diff --git a/documentation/src/main/resources/openapi/sources/schemas/properties/searchConditionProperty.yml b/documentation/src/main/resources/openapi/sources/schemas/properties/searchConditionProperty.yml new file mode 100644 index 0000000000..8fc220b266 --- /dev/null +++ b/documentation/src/main/resources/openapi/sources/schemas/properties/searchConditionProperty.yml @@ -0,0 +1,19 @@ +# Copyright (c) 2023 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0 +# +# SPDX-License-Identifier: EPL-2.0 +#filter: +description: |- + Similar to the `filter`, a `condition` may be passed to ensure strong consistency when querying things. + + This `condition` has the same syntax and semantics than the `filter` - it is however applied on the matched things + selected by the `filter` - on their current state. + + So combining this together with `filter` can provide strong consistency when performing a search. +type: string \ No newline at end of file From c1328b7fd439deac626fd4dab9fe00a0f1445fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Thu, 21 Dec 2023 17:56:48 +0100 Subject: [PATCH 5/7] fixed that "condition" query param could not be provided as form field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * which prevented long conditions to be used via POST /search/things Signed-off-by: Thomas Jäckle --- .../headers/DefaultDittoHeadersValidator.java | 2 +- .../routes/RootRouteHeadersStepBuilder.java | 9 +- .../routes/thingsearch/ThingSearchRoute.java | 30 ++++- .../thingsearch/ThingSearchRouteTest.java | 118 ++++++++++++++++-- 4 files changed, 134 insertions(+), 25 deletions(-) diff --git a/edge/service/src/main/java/org/eclipse/ditto/edge/service/headers/DefaultDittoHeadersValidator.java b/edge/service/src/main/java/org/eclipse/ditto/edge/service/headers/DefaultDittoHeadersValidator.java index 108f82ed70..b5cdb9088c 100644 --- a/edge/service/src/main/java/org/eclipse/ditto/edge/service/headers/DefaultDittoHeadersValidator.java +++ b/edge/service/src/main/java/org/eclipse/ditto/edge/service/headers/DefaultDittoHeadersValidator.java @@ -44,7 +44,7 @@ public DefaultDittoHeadersValidator(final ActorSystem actorSystem, final Config } @Override - public CompletionStage validate(DittoHeaders dittoHeaders) { + public CompletionStage validate(final DittoHeaders dittoHeaders) { checkNotNull(dittoHeaders, "dittoHeaders"); return validateSize(dittoHeaders).thenCompose(this::validateAuthorizationContext); } diff --git a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/RootRouteHeadersStepBuilder.java b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/RootRouteHeadersStepBuilder.java index 35cca7f8ff..1218421225 100644 --- a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/RootRouteHeadersStepBuilder.java +++ b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/RootRouteHeadersStepBuilder.java @@ -25,16 +25,15 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; +import org.apache.pekko.http.javadsl.model.HttpHeader; +import org.apache.pekko.http.javadsl.model.HttpMessage; +import org.apache.pekko.http.javadsl.server.RequestContext; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.headers.DittoHeadersBuilder; import org.eclipse.ditto.base.model.headers.translator.HeaderTranslator; import org.eclipse.ditto.edge.service.headers.DittoHeadersValidator; import org.eclipse.ditto.gateway.api.GatewayDuplicateHeaderException; -import org.apache.pekko.http.javadsl.model.HttpHeader; -import org.apache.pekko.http.javadsl.model.HttpMessage; -import org.apache.pekko.http.javadsl.server.RequestContext; - /** * This class provides a fluent API for building a CompletionStage that eventually supplies the {@link DittoHeaders} for * usage within the RootRoute. @@ -233,7 +232,7 @@ CompletionStage build(final CustomHeadersHandler.RequestType reque // At this point it is ensured that a correlation ID was set final String correlationId = dittoDefaultHeaders.getCorrelationId().orElseThrow(); - CompletionStage result = customHeadersHandler.handleCustomHeaders(correlationId, + final CompletionStage result = customHeadersHandler.handleCustomHeaders(correlationId, requestContext, requestType, dittoDefaultHeaders); diff --git a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRoute.java b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRoute.java index 731e47609e..5a2e041c9e 100755 --- a/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRoute.java +++ b/gateway/service/src/main/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRoute.java @@ -22,17 +22,17 @@ import javax.annotation.Nullable; +import org.apache.pekko.http.javadsl.server.Directives; +import org.apache.pekko.http.javadsl.server.PathMatchers; +import org.apache.pekko.http.javadsl.server.RequestContext; +import org.apache.pekko.http.javadsl.server.Route; +import org.eclipse.ditto.base.model.headers.DittoHeaderDefinition; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.gateway.service.endpoints.routes.AbstractRoute; import org.eclipse.ditto.gateway.service.endpoints.routes.RouteBaseProperties; import org.eclipse.ditto.thingsearch.model.signals.commands.query.CountThings; import org.eclipse.ditto.thingsearch.model.signals.commands.query.QueryThings; -import org.apache.pekko.http.javadsl.server.Directives; -import org.apache.pekko.http.javadsl.server.PathMatchers; -import org.apache.pekko.http.javadsl.server.RequestContext; -import org.apache.pekko.http.javadsl.server.Route; - /** * Builder for creating Pekko HTTP routes for {@code /search/things}. */ @@ -146,7 +146,9 @@ private Route searchThings(final RequestContext ctx, final DittoHeaders dittoHea calculateNamespaces( formFields.getOrDefault(ThingSearchParameter.NAMESPACES.toString(), List.of())), - dittoHeaders + calculateSearchPostDittoHeaders(dittoHeaders, + formFields.getOrDefault(DittoHeaderDefinition.CONDITION.getKey(), + List.of())) ) ) )) @@ -211,4 +213,20 @@ public static List calculateOptions(final List optionsString) { .toList(); } + private static DittoHeaders calculateSearchPostDittoHeaders(final DittoHeaders dittoHeaders, + final List conditionsString) { + + if (conditionsString.isEmpty()) { + return dittoHeaders; + } else { + return dittoHeaders.toBuilder() + .condition( + conditionsString + .stream() + .collect(Collectors.joining(",", "and(", ")")) + ) + .build(); + } + } + } diff --git a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRouteTest.java b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRouteTest.java index 8f822ca7f7..31a5e06191 100755 --- a/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRouteTest.java +++ b/gateway/service/src/test/java/org/eclipse/ditto/gateway/service/endpoints/routes/thingsearch/ThingSearchRouteTest.java @@ -15,6 +15,8 @@ import static org.eclipse.ditto.json.assertions.DittoJsonAssertions.assertThat; import java.util.List; +import java.util.Optional; +import java.util.function.Function; import org.apache.pekko.http.javadsl.model.FormData; import org.apache.pekko.http.javadsl.model.HttpRequest; @@ -22,9 +24,13 @@ import org.apache.pekko.http.javadsl.server.Route; import org.apache.pekko.http.javadsl.testkit.TestRoute; import org.apache.pekko.japi.Pair; +import org.eclipse.ditto.base.model.headers.WithDittoHeaders; +import org.eclipse.ditto.base.model.json.Jsonifiable; import org.eclipse.ditto.gateway.service.endpoints.EndpointTestBase; -import org.eclipse.ditto.json.JsonKey; +import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.json.JsonPointer; +import org.eclipse.ditto.json.JsonValue; import org.junit.Before; import org.junit.Test; @@ -33,9 +39,30 @@ */ public final class ThingSearchRouteTest extends EndpointTestBase { + private static final Function, Optional> DUMMY_RESPONSE_PROVIDER = + m -> DummyThingModifyCommandResponse.echo((Jsonifiable) () -> { + if (m instanceof WithDittoHeaders withDittoHeaders) { + return JsonObject.newBuilder() + .set("payload", m.toJson()) + .set("headers", withDittoHeaders.getDittoHeaders() + .toBuilder() + .removeHeader("correlation-id") + .build() + .toJson() + ) + .build(); + } else { + return m.toJson(); + } + }); + private ThingSearchRoute thingsSearchRoute; private TestRoute underTest; + @Override + protected Function, Optional> getResponseProvider() { + return DUMMY_RESPONSE_PROVIDER; + } @Before public void setUp() { @@ -48,17 +75,62 @@ public void setUp() { public void postSearchThingsShouldGetParametersFromBody() { final var formData = FormData.create( List.of( - new Pair<>("filter", "and(like(definition,\"*test*\"))"), - new Pair<>("option", "sort(+thingId,-attributes/type)"), - new Pair<>("option","limit(0,5)"), - new Pair<>("namespaces","org.eclipse.ditto,foo.bar") + new Pair<>("filter", "and(like(definition,\"*test*\"))"), + new Pair<>("option", "sort(+thingId,-attributes/type)"), + new Pair<>("option", "limit(0,5)"), + new Pair<>("namespaces", "org.eclipse.ditto,foo.bar") )); final var result = underTest.run(HttpRequest.POST("/search/things") .withEntity(formData.toEntity())); result.assertStatusCode(StatusCodes.OK); - result.assertEntity("{\"type\":\"thing-search.commands:queryThings\",\"filter\":\"and(and(like(definition,\\\"*test*\\\")))\",\"options\":[\"limit(0,5)\",\"sort(+thingId,-attributes/type)\"],\"namespaces\":[\"foo.bar\",\"org.eclipse.ditto\"]}"); + result.assertEntity(JsonObject.newBuilder() + .set("payload", JsonObject.newBuilder() + .set("type", "thing-search.commands:queryThings") + .set("filter", "and(and(like(definition,\"*test*\")))") + .set("options", JsonArray.newBuilder() + .add("limit(0,5)") + .add("sort(+thingId,-attributes/type)") + .build() + ) + .set("namespaces", JsonArray.newBuilder() + .add("foo.bar") + .add("org.eclipse.ditto") + .build() + ) + .build() + ) + .set("headers", JsonObject.empty()) + .build() + .toString() + ); } + + @Test + public void postSearchThingsShouldGetMultipleFiltersAndConditionsFromBody() { + final var formData = FormData.create( + List.of( + new Pair<>("filter", "like(definition,\"*test1*\")"), + new Pair<>("filter", "like(definition,\"*test2*\")"), + new Pair<>("condition", "like(definition,\"*test1*\")"), + new Pair<>("condition", "like(definition,\"*test2*\")") + )); + final var result = underTest.run(HttpRequest.POST("/search/things") + .withEntity(formData.toEntity())); + + result.assertStatusCode(StatusCodes.OK); + result.assertEntity(JsonObject.newBuilder() + .set("payload", JsonObject.newBuilder() + .set("type", "thing-search.commands:queryThings") + .set("filter", "and(like(definition,\"*test2*\"),like(definition,\"*test1*\"))") + .build() + ) + .set("headers", JsonObject.newBuilder() + .set("condition", "and(like(definition,\"*test2*\"),like(definition,\"*test1*\"))") + .build() + ).build().toString()); + } + @Test public void searchThingsShouldGetParametersFromUrl() { @@ -67,10 +139,29 @@ public void searchThingsShouldGetParametersFromUrl() { "namespaces=org.eclipse.ditto&" + "fields=thingId&" + "option=sort(%2Bfeature/property,-attributes/type,%2BthingId),size(2),cursor(nextCursor)") - ); + ); result.assertStatusCode(StatusCodes.OK); - result.assertEntity("{\"type\":\"thing-search.commands:queryThings\",\"options\":[\"sort(+feature/property,-attributes/type,+thingId)\",\"size(2)\",\"cursor(nextCursor)\"],\"fields\":\"/thingId\",\"namespaces\":[\"org.eclipse.ditto\"]}"); + result.assertEntity(JsonObject.newBuilder() + .set("payload", JsonObject.newBuilder() + .set("type", "thing-search.commands:queryThings") + .set("options", JsonArray.newBuilder() + .add("sort(+feature/property,-attributes/type,+thingId)") + .add("size(2)") + .add("cursor(nextCursor)") + .build() + ) + .set("fields", "/thingId") + .set("namespaces", JsonArray.newBuilder() + .add("org.eclipse.ditto") + .build() + ) + .build() + ) + .set("headers", JsonObject.empty()) + .build() + .toString() + ); } @Test @@ -78,13 +169,14 @@ public void searchThingsShouldGetFilter() { final var form = "and(and(like(definition,\"*test*\")))"; final var result = underTest.run( - HttpRequest.GET("/search/things?filter=and(like(definition,\"*test*\"))&option=sort(+thingId)&option=limit(0,5)&namespaces=org.eclipse.ditto,foo.bar")); + HttpRequest.GET( + "/search/things?filter=and(like(definition,\"*test*\"))&option=sort(+thingId)&option=limit(0,5)&namespaces=org.eclipse.ditto,foo.bar")); result.assertStatusCode(StatusCodes.OK); assertThat(JsonObject.of(result.entityString())) .contains( - JsonKey.of("filter"), + JsonPointer.of("payload/filter"), form ); } @@ -95,8 +187,8 @@ public void countThingsShouldGetFilterFromBody() { List.of( new Pair<>("filter", "and(like(definition,\"*test*\"))"), new Pair<>("option", "sort(+thingId)"), - new Pair<>("option","limit(0,5)"), - new Pair<>("namespaces","org.eclipse.ditto,foo.bar") + new Pair<>("option", "limit(0,5)"), + new Pair<>("namespaces", "org.eclipse.ditto,foo.bar") )); final var result = underTest.run(HttpRequest.POST("/search/things/count") .withEntity(formData.toEntity())); @@ -104,7 +196,7 @@ public void countThingsShouldGetFilterFromBody() { result.assertStatusCode(StatusCodes.OK); assertThat(JsonObject.of(result.entityString())) .contains( - JsonKey.of("filter"), + JsonPointer.of("payload/filter"), "and(and(like(definition,\"*test*\")))" ); } From c5ee49a95acd260fadd411a5fe89db64ce5ad1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Tue, 2 Jan 2024 10:01:05 +0100 Subject: [PATCH 6/7] prepare Ditto 3.4.4 release notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Jäckle --- .../_data/sidebars/ditto_sidebar.yml | 3 ++ .../pages/ditto/release_notes_343.md | 4 +-- .../pages/ditto/release_notes_344.md | 35 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 documentation/src/main/resources/pages/ditto/release_notes_344.md diff --git a/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml b/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml index 3dd69a345a..d52706258b 100644 --- a/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml +++ b/documentation/src/main/resources/_data/sidebars/ditto_sidebar.yml @@ -23,6 +23,9 @@ entries: - title: Release Notes output: web folderitems: + - title: 3.4.4 + url: /release_notes_344.html + output: web - title: 3.4.3 url: /release_notes_343.html output: web diff --git a/documentation/src/main/resources/pages/ditto/release_notes_343.md b/documentation/src/main/resources/pages/ditto/release_notes_343.md index 44d61f35b5..9531c8102a 100644 --- a/documentation/src/main/resources/pages/ditto/release_notes_343.md +++ b/documentation/src/main/resources/pages/ditto/release_notes_343.md @@ -7,11 +7,11 @@ summary: "Version 3.4.3 of Eclipse Ditto, released on 05.12.2023" permalink: release_notes_343.html --- -This is a bugfix release, no new features since [3.4.2](release_notes_341.html) were added. +This is a bugfix release, no new features since [3.4.2](release_notes_342.html) were added. ## Changelog -Compared to the latest release [3.4.2](release_notes_341.html), the following changes and bugfixes were added. +Compared to the latest release [3.4.2](release_notes_342.html), the following changes and bugfixes were added. ### Changes diff --git a/documentation/src/main/resources/pages/ditto/release_notes_344.md b/documentation/src/main/resources/pages/ditto/release_notes_344.md new file mode 100644 index 0000000000..aa0121e0ed --- /dev/null +++ b/documentation/src/main/resources/pages/ditto/release_notes_344.md @@ -0,0 +1,35 @@ +--- +title: Release notes 3.4.4 +tags: [release_notes] +published: true +keywords: release notes, announcements, changelog +summary: "Version 3.4.4 of Eclipse Ditto, released on 02.01.2024" +permalink: release_notes_344.html +--- + +This is a bugfix release, no new features since [3.4.3](release_notes_343.html) were added. + +## Changelog + +Compared to the latest release [3.4.3](release_notes_343.html), the following changes and bugfixes were added. + +### Changes + + +### Bugfixes + +This is a complete list of the +[merged pull requests](https://github.com/eclipse-ditto/ditto/pulls?q=is%3Apr+milestone%3A3.4.4). + +#### Fetching a thing at a non-existing historical revision (e.g. in future) results in status 503 + +Bug [#1844](https://github.com/eclipse-ditto/ditto/issues/1844) caused that a request for a non-existing +`at-historical-revision` resulted in an HTTP status `503` and caused the actor to be blocked for 15 seconds. +This was fixed in PR [#1845](https://github.com/eclipse-ditto/ditto/pull/1845). + +#### Fixed that "condition" query param could not be provided as form field + +PR [#1848](https://github.com/eclipse-ditto/ditto/pull/1848) fixes that - when using the `POST /search/things` endpoint +the `condition` query parameter could not be passed as form field - which again leads to potentially too large HTTP urls +when providing a very long query/condition RQL. + From aba53adf779a5dd6cad8c8548457097f7905969d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20J=C3=A4ckle?= Date: Tue, 2 Jan 2024 13:36:14 +0100 Subject: [PATCH 7/7] bump Helm chart versions to 3.4.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Jäckle --- deployment/helm/ditto/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/helm/ditto/Chart.yaml b/deployment/helm/ditto/Chart.yaml index 76a499ffe0..0e73c5a1ae 100644 --- a/deployment/helm/ditto/Chart.yaml +++ b/deployment/helm/ditto/Chart.yaml @@ -16,8 +16,8 @@ description: | A digital twin is a virtual, cloud based, representation of his real world counterpart (real world “Things”, e.g. devices like sensors, smart heating, connected cars, smart grids, EV charging stations etc). type: application -version: 3.4.3 # chart version is effectively set by release-job -appVersion: 3.4.3 +version: 3.4.4 # chart version is effectively set by release-job +appVersion: 3.4.4 keywords: - iot-chart - digital-twin