use of org.eclipse.ditto.messages.model.signals.commands.MessageCommand in project ditto by eclipse.
the class OutboundMappingProcessorTest method testOutboundLiveMessageWithRequestedAcksWhichAreIssuedByTargetDontContainRequestedAcks.
@Test
@SuppressWarnings({ "unchecked", "rawtypes", "java:S3740" })
public void testOutboundLiveMessageWithRequestedAcksWhichAreIssuedByTargetDontContainRequestedAcks() {
new TestKit(actorSystem) {
{
final AcknowledgementLabel customAckLabel = AcknowledgementLabel.of("custom:ack");
final AcknowledgementLabel targetIssuedAckLabel = AcknowledgementLabel.of("issued:ack");
final DittoHeaders dittoHeaders = DittoHeaders.newBuilder().channel(TopicPath.Channel.LIVE.getName()).acknowledgementRequest(AcknowledgementRequest.of(targetIssuedAckLabel), AcknowledgementRequest.of(customAckLabel)).build();
final Message<Object> message = Message.newBuilder(MessageHeaders.newBuilder(MessageDirection.TO, TestConstants.Things.THING_ID, "ditto").acknowledgementRequest(AcknowledgementRequest.of(targetIssuedAckLabel), AcknowledgementRequest.of(customAckLabel)).build()).build();
final MessageCommand signal = SendThingMessage.of(TestConstants.Things.THING_ID, message, dittoHeaders);
final OutboundSignal outboundSignal = Mockito.mock(OutboundSignal.class);
final MappingOutcome.Visitor<OutboundSignal.Mapped, Void> mock = Mockito.mock(MappingOutcome.Visitor.class);
when(outboundSignal.getSource()).thenReturn(signal);
when(outboundSignal.getTargets()).thenReturn(List.of(ConnectivityModelFactory.newTargetBuilder().address("test").issuedAcknowledgementLabel(targetIssuedAckLabel).authorizationContext(AuthorizationContext.newInstance(DittoAuthorizationContextType.UNSPECIFIED, AuthorizationSubject.newInstance("issuer:subject"))).topics(Topic.TWIN_EVENTS).build()));
underTest.process(outboundSignal).forEach(outcome -> outcome.accept(mock));
final ArgumentCaptor<OutboundSignal.Mapped> captor = ArgumentCaptor.forClass(OutboundSignal.Mapped.class);
verify(mock, times(1)).onMapped(any(String.class), captor.capture());
verify(mock, times(0)).onError(any(String.class), any(Exception.class), any(), any());
verify(mock, times(0)).onDropped(any(String.class), any());
assertThat(captor.getAllValues()).allSatisfy(em -> assertThat(em.getAdaptable().getDittoHeaders().getAcknowledgementRequests()).containsAll(List.of(AcknowledgementRequest.of(customAckLabel), AcknowledgementRequest.of(targetIssuedAckLabel))));
}
};
}
use of org.eclipse.ditto.messages.model.signals.commands.MessageCommand in project ditto by eclipse.
the class LiveSignalEnforcement method doEnforce.
private CompletionStage<Contextual<WithDittoHeaders>> doEnforce(final SignalWithEntityId<?> liveSignal, final Entry<Enforcer> enforcerEntry) {
final CompletionStage<Contextual<WithDittoHeaders>> result;
final var correlationIdOpt = SignalInformationPoint.getCorrelationId(liveSignal);
if (enforcerEntry.exists() && correlationIdOpt.isPresent()) {
final Enforcer enforcer = enforcerEntry.getValueOrThrow();
if (liveSignal instanceof SendClaimMessage) {
// claim messages require no enforcement, publish them right away:
result = publishMessageCommand((MessageCommand<?, ?>) liveSignal, enforcer);
} else if (SignalInformationPoint.isCommandResponse(liveSignal)) {
result = enforceLiveCommandResponse((CommandResponse<?>) liveSignal, correlationIdOpt.get());
} else {
final var streamingType = StreamingType.fromSignal(liveSignal);
if (streamingType.isPresent()) {
result = enforceLiveSignal(streamingType.get(), liveSignal, enforcer);
} else {
log().error("Unsupported Signal in LiveSignalEnforcement: <{}>", liveSignal);
throw GatewayInternalErrorException.newBuilder().dittoHeaders(liveSignal.getDittoHeaders()).build();
}
}
} else {
// drop live command to nonexistent things and respond with error.
log(liveSignal).info("Command of type <{}> with ID <{}> could not be dispatched as no enforcer " + "could be looked up! Answering with ThingNotAccessibleException.", liveSignal.getType(), liveSignal.getEntityId());
throw ThingNotAccessibleException.newBuilder(ThingId.of(entityId().getId())).dittoHeaders(liveSignal.getDittoHeaders()).build();
}
return result;
}
use of org.eclipse.ditto.messages.model.signals.commands.MessageCommand in project ditto by eclipse.
the class ThingsSseRouteBuilder method createMessagesSseRoute.
private Route createMessagesSseRoute(final RequestContext ctx, final CompletionStage<DittoHeaders> dittoHeadersStage, final String thingId, final String messagePath) {
final List<ThingId> targetThingIds = List.of(ThingId.of(thingId));
final CompletionStage<SignalEnrichmentFacade> facadeStage = signalEnrichmentProvider == null ? CompletableFuture.completedStage(null) : signalEnrichmentProvider.getFacade(ctx.getRequest());
final var sseSourceStage = facadeStage.thenCompose(facade -> dittoHeadersStage.thenCompose(dittoHeaders -> sseAuthorizationEnforcer.checkAuthorization(ctx, dittoHeaders).thenApply(unused -> {
final Source<SessionedJsonifiable, SupervisedStream.WithQueue> publisherSource = SupervisedStream.sourceQueue(10);
return publisherSource.viaMat(KillSwitches.single(), Keep.both()).mapMaterializedValue(pair -> {
final SupervisedStream.WithQueue withQueue = pair.first();
final KillSwitch killSwitch = pair.second();
final String connectionCorrelationId = dittoHeaders.getCorrelationId().orElseThrow(() -> new IllegalStateException("Expected correlation-id in SSE DittoHeaders: " + dittoHeaders));
final var jsonSchemaVersion = dittoHeaders.getSchemaVersion().orElse(JsonSchemaVersion.LATEST);
sseConnectionSupervisor.supervise(withQueue.getSupervisedStream(), connectionCorrelationId, dittoHeaders);
final var authorizationContext = dittoHeaders.getAuthorizationContext();
final var connect = new Connect(withQueue.getSourceQueue(), connectionCorrelationId, STREAMING_TYPE_SSE, jsonSchemaVersion, null, Set.of(), authorizationContext);
final String resourcePathRqlStatement;
if (INBOX_OUTBOX_WITH_SUBJECT_PATTERN.matcher(messagePath).matches()) {
resourcePathRqlStatement = String.format("eq(resource:path,'%s')", messagePath);
} else {
resourcePathRqlStatement = String.format("like(resource:path,'%s*')", messagePath);
}
final var startStreaming = StartStreaming.getBuilder(StreamingType.MESSAGES, connectionCorrelationId, authorizationContext).withFilter(MessageFormat.format("and(" + "eq(entity:id,''{0}'')," + "{1}" + ")", thingId, resourcePathRqlStatement)).build();
Patterns.ask(streamingActor, connect, LOCAL_ASK_TIMEOUT).thenApply(ActorRef.class::cast).thenAccept(streamingSessionActor -> streamingSessionActor.tell(startStreaming, ActorRef.noSender())).exceptionally(e -> {
killSwitch.abort(e);
return null;
});
return NotUsed.getInstance();
}).filter(jsonifiable -> jsonifiable.getJsonifiable() instanceof MessageCommand).mapAsync(streamingConfig.getParallelism(), jsonifiable -> postprocessMessages(targetThingIds, (MessageCommand<?, ?>) jsonifiable.getJsonifiable(), facade, jsonifiable)).mapConcat(messages -> messages).map(message -> {
THINGS_SSE_COUNTER.increment();
final Optional<Charset> charset = determineCharsetFromContentType(message.getContentType());
final String data = message.getRawPayload().map(body -> new String(body.array(), charset.orElse(StandardCharsets.UTF_8))).orElse("");
return ServerSentEvent.create(data, message.getSubject());
}).log("SSE " + PATH_THINGS + "/" + messagePath).keepAlive(Duration.ofSeconds(1), ServerSentEvent::heartbeat);
})));
return completeOKWithFuture(sseSourceStage, EventStreamMarshalling.toEventStream());
}
use of org.eclipse.ditto.messages.model.signals.commands.MessageCommand in project ditto by eclipse.
the class HttpPublisherActor method toCommandResponseOrAcknowledgement.
private CompletionStage<SendResult> toCommandResponseOrAcknowledgement(final Signal<?> sentSignal, @Nullable final Target autoAckTarget, final HttpResponse response, final int maxTotalMessageSize, final int ackSizeQuota, final AuthorizationContext targetAuthorizationContext) {
final var autoAckLabel = getAcknowledgementLabel(autoAckTarget);
final var statusCode = response.status().intValue();
final HttpStatus httpStatus;
try {
httpStatus = HttpStatus.getInstance(statusCode);
} catch (final HttpStatusCodeOutOfRangeException e) {
response.discardEntityBytes(materializer);
final var error = MessageSendingFailedException.newBuilder().message(String.format("Remote server delivered unknown HTTP status code <%d>!", statusCode)).cause(e).build();
return CompletableFuture.failedFuture(error);
}
final var isSentSignalLiveCommand = SignalInformationPoint.isLiveCommand(sentSignal);
final int maxResponseSize = isSentSignalLiveCommand ? maxTotalMessageSize : ackSizeQuota;
return getResponseBody(response, maxResponseSize, materializer).thenApply(body -> {
@Nullable final CommandResponse<?> result;
final var mergedDittoHeaders = mergeWithResponseHeaders(sentSignal.getDittoHeaders(), response);
final Optional<EntityId> entityIdOptional = WithEntityId.getEntityIdOfType(EntityId.class, sentSignal);
if (autoAckLabel.isPresent() && entityIdOptional.isPresent()) {
final EntityId entityId = entityIdOptional.get();
if (DittoAcknowledgementLabel.LIVE_RESPONSE.equals(autoAckLabel.get())) {
// Live-Response is declared as issued ack => parse live response from response
if (sentSignal instanceof MessageCommand) {
result = toMessageCommandResponse((MessageCommand<?, ?>) sentSignal, mergedDittoHeaders, body, httpStatus, targetAuthorizationContext);
} else if (sentSignal instanceof ThingCommand && SignalInformationPoint.isChannelLive(sentSignal)) {
result = toLiveCommandResponse(mergedDittoHeaders, body, targetAuthorizationContext);
} else {
result = null;
}
} else {
// There is an issued ack declared but its not live-response => handle response as acknowledgement.
result = Acknowledgement.of(autoAckLabel.get(), entityId, httpStatus, mergedDittoHeaders, body);
}
} else {
// No Acks declared as issued acks => Handle response either as live response or as acknowledgement
// or as fallback build a response for local diagnostics.
final boolean isDittoProtocolMessage = mergedDittoHeaders.getDittoContentType().filter(org.eclipse.ditto.base.model.headers.contenttype.ContentType::isDittoProtocol).isPresent();
if (isDittoProtocolMessage && body.isObject()) {
final CommandResponse<?> parsedResponse = toCommandResponse(body.asObject(), targetAuthorizationContext);
if (parsedResponse instanceof Acknowledgement) {
result = parsedResponse;
} else if (SignalInformationPoint.isLiveCommandResponse(parsedResponse)) {
result = parsedResponse;
} else {
result = null;
}
} else {
result = null;
}
}
final var liveCommandWithEntityId = tryToGetAsLiveCommandWithEntityId(sentSignal);
if (liveCommandWithEntityId.isPresent() && null != result && SignalInformationPoint.isLiveCommandResponse(result)) {
// Do only return command response for live commands with a correct response.
httpPushRoundTripSignalValidator.accept((Command<?>) liveCommandWithEntityId.get(), result);
}
if (result == null) {
connectionLogger.success(InfoProviderFactory.forSignal(sentSignal), "No CommandResponse created from HTTP response with status <{0}> and body <{1}>.", response.status(), body);
} else {
connectionLogger.success(InfoProviderFactory.forSignal(result), "CommandResponse <{0}> created from HTTP response with Status <{1}> and body <{2}>.", result, response.status(), body);
}
final MessageSendingFailedException sendFailure;
if (!httpStatus.isSuccess()) {
final String message = String.format("Got non success status code: <%s> and body: <%s>", httpStatus.getCode(), body);
sendFailure = MessageSendingFailedException.newBuilder().message(message).dittoHeaders(mergedDittoHeaders).build();
} else {
sendFailure = null;
}
return new SendResult(result, sendFailure, mergedDittoHeaders);
});
}
Aggregations