INT-4255: JSON Embedded Headers Message Mapper

JIRA: https://jira.spring.io/browse/INT-4255

- Support embedding headers for transports that don't support headers (TCP, Kafka, etc)
- Use the new message-aware Jackson ObjectMapper
- Provide a mechanism to more efficiently support byte[] payloads (avoid Base64 encoding)
- Support decoding "legacy" SCSt embedded headers

Polishing; PR Comments - Add Support to MQTT and TCP

Switch to simple patterns instead of regex

* Fix JavaDoc typo
* Upgrade to Jackson 2.9.1
This commit is contained in:
Gary Russell
2017-05-21 12:56:41 -04:00
committed by Artem Bilan
parent 0d495294ed
commit 73d9c6b6c5
10 changed files with 602 additions and 20 deletions

View File

@@ -21,11 +21,13 @@ import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.mapping.BytesMessageMapper;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.util.Assert;
@@ -51,6 +53,8 @@ public class DefaultPahoMessageConverter implements MqttMessageConverter, BeanFa
private final MessageProcessor<Boolean> retainedProcessor;
private BytesMessageMapper bytesMessageMapper;
private volatile boolean payloadAsBytes = false;
private volatile BeanFactory beanFactory;
@@ -164,8 +168,10 @@ public class DefaultPahoMessageConverter implements MqttMessageConverter, BeanFa
/**
* True if the converter should not convert the message payload to a String.
* Ignored if a {@link BytesMessageMapper} is provided.
*
* @param payloadAsBytes The payloadAsBytes to set.
* @see #setBytesMessageMapper(BytesMessageMapper)
*/
public void setPayloadAsBytes(boolean payloadAsBytes) {
this.payloadAsBytes = payloadAsBytes;
@@ -175,6 +181,18 @@ public class DefaultPahoMessageConverter implements MqttMessageConverter, BeanFa
return this.payloadAsBytes;
}
/**
* Set a {@link BytesMessageMapper} to use when mapping byte[].
* {@link #setPayloadAsBytes(boolean)} is ignored when a {@link BytesMessageMapper}
* is provided.
* @param bytesMessageMapper the mapper.
* @since 5.0
* @see #setPayloadAsBytes(boolean)
*/
public void setBytesMessageMapper(BytesMessageMapper bytesMessageMapper) {
this.bytesMessageMapper = bytesMessageMapper;
}
@Override
public Message<?> toMessage(Object mqttMessage, MessageHeaders headers) {
Assert.isInstanceOf(MqttMessage.class, mqttMessage,
@@ -183,11 +201,20 @@ public class DefaultPahoMessageConverter implements MqttMessageConverter, BeanFa
return toMessage(null, (MqttMessage) mqttMessage);
}
@SuppressWarnings("unchecked")
@Override
public Message<?> toMessage(String topic, MqttMessage mqttMessage) {
try {
AbstractIntegrationMessageBuilder<Object> messageBuilder = getMessageBuilderFactory()
.withPayload(mqttBytesToPayload(mqttMessage))
AbstractIntegrationMessageBuilder<Object> messageBuilder;
if (this.bytesMessageMapper != null) {
messageBuilder = (AbstractIntegrationMessageBuilder<Object>) getMessageBuilderFactory()
.fromMessage(this.bytesMessageMapper.toMessage(mqttMessage.getPayload()));
}
else {
messageBuilder = getMessageBuilderFactory()
.withPayload(mqttBytesToPayload(mqttMessage));
}
messageBuilder
.setHeader(MqttHeaders.RECEIVED_QOS, mqttMessage.getQos())
.setHeader(MqttHeaders.DUPLICATE, mqttMessage.isDuplicate())
.setHeader(MqttHeaders.RECEIVED_RETAINED, mqttMessage.isRetained());
@@ -232,29 +259,42 @@ public class DefaultPahoMessageConverter implements MqttMessageConverter, BeanFa
/**
* Subclasses can override this method to convert the payload to a byte[].
* The default implementation accepts a byte[] or String payload.
* If a {@link BytesMessageMapper} is provided, conversion to byte[]
* is delegated to it, so any payload that it can handle is supported.
*
* @param message The outbound Message.
* @return The byte[] which will become the payload of the MQTT Message.
*/
protected byte[] messageToMqttBytes(Message<?> message) {
Object payload = message.getPayload();
Assert.isTrue(payload instanceof byte[] || payload instanceof String,
() -> "This default converter can only handle 'byte[]' or 'String' payloads; consider adding a "
+ "transformer to your flow definition, or subclass this converter for "
+ payload.getClass().getName() + " payloads");
byte[] payloadBytes;
if (payload instanceof String) {
if (this.bytesMessageMapper != null) {
try {
payloadBytes = ((String) payload).getBytes(this.charset);
return this.bytesMessageMapper.fromMessage(message);
}
catch (Exception e) {
throw new MessageConversionException("failed to convert Message to object", e);
throw new MessageHandlingException(message, "Failed to map outbound message", e);
}
}
else {
payloadBytes = (byte[]) payload;
Object payload = message.getPayload();
Assert.isTrue(payload instanceof byte[] || payload instanceof String,
() -> "This default converter can only handle 'byte[]' or 'String' payloads; consider adding a "
+ "transformer to your flow definition, or provide a BytesMessageMapper, "
+ "or subclass this converter for "
+ payload.getClass().getName() + " payloads");
byte[] payloadBytes;
if (payload instanceof String) {
try {
payloadBytes = ((String) payload).getBytes(this.charset);
}
catch (Exception e) {
throw new MessageConversionException("failed to convert Message to object", e);
}
}
else {
payloadBytes = (byte[]) payload;
}
return payloadBytes;
}
return payloadBytes;
}
}