Skip to content

Commit

Permalink
Issue eclipse-ditto#777: add fallback to DittoMessageMapper based on …
Browse files Browse the repository at this point in the history
…signal and content type.

Signed-off-by: Yufei Cai <yufei.cai@bosch.io>
  • Loading branch information
yufei-cai committed Aug 30, 2020
1 parent 9660b95 commit 3ca0e86
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 32 deletions.
Expand Up @@ -47,7 +47,7 @@
public final class DittoMessageMapper extends AbstractMessageMapper {


private static final JsonObject DEFAULT_OPTIONS = JsonObject.newBuilder()
static final JsonObject DEFAULT_OPTIONS = JsonObject.newBuilder()
.set(MessageMapperConfiguration.CONTENT_TYPE_BLOCKLIST,
String.join(",", "application/vnd.eclipse-hono-empty-notification",
"application/vnd.eclipse-hono-dc-notification+json"))
Expand Down
Expand Up @@ -67,7 +67,9 @@ public final class RawMessageMapper extends AbstractMessageMapper {
private static final String INCOMING_MESSAGE_HEADERS = "incomingMessageHeaders";

/**
* Default outgoing content type is text/plain because binary requires base64 encoded string as payload.
* Default outgoing content type is text/plain because binary requires base64 encoded string as payload,
* which cannot be satisfied by all message commands and responses in the Ditto protocol, whereas
* the text/plain content type can.
*/
private static final String DEFAULT_OUTGOING_CONTENT_TYPE = ContentTypes.TEXT_PLAIN_UTF8.toString();

Expand All @@ -85,7 +87,7 @@ public final class RawMessageMapper extends AbstractMessageMapper {
MessageHeaderDefinition.FEATURE_ID.getKey(), asPlaceholder(MessageHeaderDefinition.FEATURE_ID)
);

private static final JsonObject DEFAULT_CONFIG = JsonObject.newBuilder()
private static final JsonObject DEFAULT_CONFIG = DittoMessageMapper.DEFAULT_OPTIONS.toBuilder()
.set(OUTGOING_CONTENT_TYPE_KEY, DEFAULT_OUTGOING_CONTENT_TYPE)
.set(INCOMING_MESSAGE_HEADERS, DEFAULT_INCOMING_HEADERS.entrySet()
.stream()
Expand All @@ -108,6 +110,8 @@ public final class RawMessageMapper extends AbstractMessageMapper {
*/
private Map<String, String> incomingMessageHeaders = DEFAULT_INCOMING_HEADERS;

private final DittoMessageMapper dittoMessageMapper = new DittoMessageMapper();

/**
* The context representing this mapper.
*/
Expand All @@ -122,8 +126,7 @@ public List<Adaptable> map(final ExternalMessage externalMessage) {
evaluateIncomingMessageHeaders(externalMessage, incomingMessageHeaders);
if (messageHeadersOptional.isEmpty()) {
// message payload is a Ditto protocol message.
return List.of(ProtocolFactory.jsonifiableAdaptableFromJson(
JsonObject.of(externalMessage.getTextPayload().orElseThrow())));
return dittoMessageMapper.map(externalMessage);
}
final MessageHeaders messageHeaders = messageHeadersOptional.get();
return List.of(ProtocolFactory.newAdaptableBuilder(toTopicPath(messageHeaders))
Expand All @@ -138,26 +141,27 @@ public List<ExternalMessage> map(final Adaptable adaptable) {
final String contentType = adaptable.getDittoHeaders()
.getContentType()
.orElse(fallbackOutgoingContentType);
final ExternalMessageBuilder builder =
ExternalMessageFactory.newExternalMessageBuilder(
evaluateOutgoingMessageHeaders(adaptable, contentType))
.withInternalHeaders(adaptable.getDittoHeaders());
adaptable.getPayload().getValue().ifPresent(payloadValue -> {
if (MessageDeserializer.shouldBeInterpretedAsTextOrJson(contentType)) {
builder.withText(toOutgoingText(payloadValue));
} else {
// binary payload only possible if payload is a base64-encoded string.
builder.withBytes(Optional.of(payloadValue)
.filter(JsonValue::isString)
.flatMap(value -> toOutgoingBinary(value.asString()))
.orElseThrow(() -> badContentType(contentType, adaptable.getDittoHeaders()))
);
}
});
return List.of(builder.build());
} else {
return List.of();
if (!DITTO_PROTOCOL_CONTENT_TYPE.equalsIgnoreCase(contentType)) {
final ExternalMessageBuilder builder =
ExternalMessageFactory.newExternalMessageBuilder(
evaluateOutgoingMessageHeaders(adaptable, contentType))
.withInternalHeaders(adaptable.getDittoHeaders());
adaptable.getPayload().getValue().ifPresent(payloadValue -> {
if (MessageDeserializer.shouldBeInterpretedAsTextOrJson(contentType)) {
builder.withText(toOutgoingText(payloadValue));
} else {
// binary payload only possible if payload is a base64-encoded string.
builder.withBytes(Optional.of(payloadValue)
.filter(JsonValue::isString)
.flatMap(value -> toOutgoingBinary(value.asString()))
.orElseThrow(() -> badContentType(contentType, adaptable.getDittoHeaders()))
);
}
});
return List.of(builder.build());
}
}
return dittoMessageMapper.map(adaptable);
}

@Override
Expand All @@ -167,6 +171,7 @@ public JsonObject getDefaultOptions() {

@Override
protected void doConfigure(final MappingConfig mappingConfig, final MessageMapperConfiguration configuration) {
dittoMessageMapper.configure(mappingConfig, configuration);
fallbackOutgoingContentType =
configuration.findProperty(OUTGOING_CONTENT_TYPE_KEY).orElse(fallbackOutgoingContentType);
configuration.findProperty(INCOMING_MESSAGE_HEADERS, JsonValue::isObject, JsonValue::asObject)
Expand Down
Expand Up @@ -40,6 +40,7 @@
import org.eclipse.ditto.signals.commands.messages.SendFeatureMessageResponse;
import org.eclipse.ditto.signals.commands.messages.SendThingMessage;
import org.eclipse.ditto.signals.commands.things.modify.DeleteThingResponse;
import org.eclipse.ditto.signals.events.things.ThingDeleted;
import org.junit.Before;
import org.junit.Test;

Expand All @@ -63,34 +64,37 @@ public void setUp() {
@Test
public void mapFromMessageWithoutPayloadWithoutContentType() {
final DittoHeaders dittoHeaders = DittoHeaders.newBuilder().randomCorrelationId().build();
final Message<Object> messageWithoutPayload = messageBuilder(null).build();
final Signal<?> sendThingMessage = SendThingMessage.of(THING_ID, messageWithoutPayload, dittoHeaders);
final Message<Object> message = messageBuilder(null).build();
final Signal<?> sendThingMessage = SendThingMessage.of(THING_ID, message, dittoHeaders);
final List<ExternalMessage> result = underTest.map(ADAPTER.toAdaptable(sendThingMessage));
assertThat(result).hasSize(1);
assertThat(result.get(0).getBytePayload()).isEmpty();
assertThat(result.get(0).getTextPayload()).isEmpty();
final ExternalMessage externalMessage = result.get(0);
assertThat(externalMessage.getBytePayload()).isEmpty();
assertThat(externalMessage.getTextPayload()).isEmpty();
assertThat(externalMessage.getHeaders()).containsAllEntriesOf(message.getHeaders());
}

@Test
public void mapFromMessageWithTextPayload() {
final DittoHeaders dittoHeaders = DittoHeaders.newBuilder().randomCorrelationId().build();
final Message<Object> messageWithoutPayload = messageBuilder("application/vnd.eclipse.ditto+json")
final Message<Object> message = messageBuilder("text/plain")
.payload("hello world")
.build();
final Signal<?> sendThingMessage = SendThingMessage.of(THING_ID, messageWithoutPayload, dittoHeaders);
final Signal<?> sendThingMessage = SendThingMessage.of(THING_ID, message, dittoHeaders);
final List<ExternalMessage> result = underTest.map(ADAPTER.toAdaptable(sendThingMessage));
assertThat(result).hasSize(1);
assertThat(result.get(0).getBytePayload()).isEmpty();
assertThat(result.get(0).getTextPayload()).contains("hello world");
assertThat(result.get(0).getHeaders()).containsExactlyEntriesOf(message.getHeaders());
}

@Test
public void mapFromMessageWithBinaryPayload() {
final DittoHeaders dittoHeaders = DittoHeaders.newBuilder().randomCorrelationId().build();
final Message<Object> messageWithoutPayload = messageBuilder("application/whatever")
final Message<Object> message = messageBuilder("application/whatever")
.rawPayload(ByteBuffer.wrap(new byte[]{1, 2, 3, 4}))
.build();
final Signal<?> sendThingMessage = SendThingMessage.of(THING_ID, messageWithoutPayload, dittoHeaders);
final Signal<?> sendThingMessage = SendThingMessage.of(THING_ID, message, dittoHeaders);
final List<ExternalMessage> result = underTest.map(ADAPTER.toAdaptable(sendThingMessage));
assertThat(result).hasSize(1);
assertThat(result.get(0).getBytePayload()).satisfies(byteBufferOptional -> {
Expand All @@ -99,6 +103,7 @@ public void mapFromMessageWithBinaryPayload() {
.isEqualTo(ByteString.copyFrom(new byte[]{1, 2, 3, 4}));
});
assertThat(result.get(0).getTextPayload()).isEmpty();
assertThat(result.get(0).getHeaders()).containsAllEntriesOf(message.getHeaders());
}

@Test
Expand Down Expand Up @@ -129,6 +134,28 @@ public void mapFromTextMessageWithBinaryContentType() {
assertThatExceptionOfType(MessageFormatInvalidException.class).isThrownBy(() -> underTest.map(adaptable));
}

@Test
public void mapFromNonMessageCommand() {
final Signal<?> signal = ThingDeleted.of(ThingId.of("thing:id"), 25L, DittoHeaders.empty());
final Adaptable adaptable = ADAPTER.toAdaptable(signal);
final List<ExternalMessage> actualExternalMessages = underTest.map(adaptable);
final List<ExternalMessage> expectedExternalMessages = new DittoMessageMapper().map(adaptable);
assertThat(actualExternalMessages).isEqualTo(expectedExternalMessages);
}

@Test
public void mapFromMessageWithDittoProtocolContentType() {
final DittoHeaders dittoHeaders = DittoHeaders.newBuilder().randomCorrelationId().build();
final Message<Object> messageWithoutPayload = messageBuilder("application/vnd.eclipse.ditto+json")
.payload("hello world")
.build();
final Signal<?> sendThingMessage = SendThingMessage.of(THING_ID, messageWithoutPayload, dittoHeaders);
final Adaptable adaptable = ADAPTER.toAdaptable(sendThingMessage);
final List<ExternalMessage> result = underTest.map(adaptable);
final List<ExternalMessage> expectedResult = new DittoMessageMapper().map(adaptable);
assertThat(result).isEqualTo(expectedResult);
}

@Test
public void mapToMessageWithoutHeadersOrConfig() {
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->
Expand Down Expand Up @@ -252,6 +279,17 @@ public void mapToSendFeatureMessageResponse() {
assertThat(response.getMessage().getPayload().orElseThrow()).isEqualTo(JsonObject.of(payload));
}

@Test
public void mapToNonMessageCommandWithDittoProtocolContentType() {
final Signal<?> signal = ThingDeleted.of(ThingId.of("thing:id"), 25L, DittoHeaders.empty());
final Adaptable adaptable = ADAPTER.toAdaptable(signal);
final ExternalMessage externalMessage = new DittoMessageMapper().map(adaptable).get(0)
.withHeader("content-type", "application/vnd.eclipse.ditto+json");
final List<Adaptable> mapped = underTest.map(externalMessage);
assertThat(mapped).hasSize(1);
assertThat(mapped.get(0)).isEqualTo(adaptable);
}

private MessageBuilder<Object> messageBuilder(@Nullable final String contentType) {
return MessagesModelFactory.newMessageBuilder(
MessagesModelFactory.newHeadersBuilder(MessageDirection.TO, THING_ID, "subject")
Expand Down

0 comments on commit 3ca0e86

Please sign in to comment.