INT-1807 Add Mechanism For Headers with TCP

TCP streams have no standard message structure. Therefore, the
TCP implementation previously only transferred the message
payload.

If someone wanted to convey header information, they would have
to write their own wrapper and/or use Java serialization for
the entire message.

This change provides a strategy to allow users to determine
which headers are transferred, and how.

A MessageConvertingMessageMapper is now provided that invokes
any MessageConverter. A MapMessageConverter is provided that
converts the payload, and selected heades to a Map with two
entries ("payload") and ("headers").

A MapJsonSerializer is provided that converts a Map to/from
JSON. Jackson can't delimit multiple objects in a stream
so another serializer is required to encode/decode structure.
A ByteArrayLfSerializer is used by default, inserting a
linefeed between JSON objects.

The combination of these elements now allows header
information to be transferred over TCP. Of course, users
can implment their own (de)serializer to format the
bits on the wire exactly as needed by their application.

INT-1807 Polishing

Add a test that uses a Map MessageConverter with a
Java (de)serializer.

INT-1807: Polishing

INT-1807: Rebased and polished

Change `MapJsonSerializer` to use `JsonObjectMapper` abstraction

Doc Polishing
This commit is contained in:
Gary Russell
2013-03-09 14:15:27 -05:00
committed by Gary Russell
parent 1847eaa194
commit 0699fdc6cf
11 changed files with 835 additions and 25 deletions

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.support.converter;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
/**
* Converts to/from a Map with 2 keys ('headers' and 'payload').
* @author Gary Russell
* @since 3.0
*
*/
public class MapMessageConverter implements MessageConverter {
private volatile String[] headerNames;
private volatile boolean filterHeadersInToMessage;
/**
* Headers to be converted in {@link #fromMessage(Message)}.
* {@link #toMessage(Object)} will populate all headers found in
* the map, unless {@link #filterHeadersInToMessage} is true.
* @param headerNames
*/
public void setHeaderNames(String... headerNames) {
this.headerNames = headerNames;
}
/**
* By default all headers on Map passed to {@link #toMessage(Object)}
* will be mapped. Set this property
* to 'true' if you wish to limit the inbound headers to those in
* the #headerNames.
* @param filterHeadersInToMessage
*/
public void setFilterHeadersInToMessage(boolean filterHeadersInToMessage) {
this.filterHeadersInToMessage = filterHeadersInToMessage;
}
public <P> Message<P> toMessage(Object object) {
Assert.isInstanceOf(Map.class, object, "This converter expects a Map");
@SuppressWarnings("unchecked")
Map<String, ?> map = (Map<String, ?>) object;
Object payload = map.get("payload");
Assert.notNull(payload, "'payload' entry cannot be null");
MessageBuilder<?> messageBuilder = MessageBuilder.withPayload(payload);
@SuppressWarnings("unchecked")
Map<String, ?> headers = (Map<String, ?>) map.get("headers");
if (headers != null) {
if (this.filterHeadersInToMessage) {
headers.keySet().retainAll(Arrays.asList(this.headerNames));
}
messageBuilder.copyHeaders(headers);
/*for (Entry<String, ?> entry : headers.entrySet()) {
if (this.filterHeadersInToMessage ? this.headerNames.contains(entry.getKey()) : true) {
messageBuilder.setHeader(entry.getKey(), entry.getValue());
}
}*/
}
@SuppressWarnings("unchecked")
Message<P> convertedMessage = (Message<P>) messageBuilder.build();
return convertedMessage;
}
public <P> Object fromMessage(Message<P> message) {
Map<String,Object> map = new HashMap<String, Object>();
map.put("payload", message.getPayload());
Map<String, Object> headers = new HashMap<String, Object>();
for (String headerName : headerNames) {
Object header = message.getHeaders().get(headerName);
if (header != null) {
headers.put(headerName, header);
}
}
map.put("headers", headers);
return map;
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.support.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.util.Map;
import org.junit.Test;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
/**
* @author Gary Russell
* @author Artem Bilan
* @since 3.0
*
*/
public class MapMessageConverterTests {
@Test
public void testFromMessageToMessage() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo")
.setHeader("bar", "baz")
.setHeader("baz", "qux")
.build();
MapMessageConverter converter = new MapMessageConverter();
converter.setHeaderNames("bar");
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) converter.fromMessage(message);
@SuppressWarnings("unchecked")
Map<String, Object> headers = (Map<String, Object>) map.get("headers");
assertNotNull(headers);
assertNotNull(map.get("payload"));
assertEquals("foo", map.get("payload"));
assertNotNull(headers.get("bar"));
assertEquals("baz", headers.get("bar"));
assertNull(headers.get("baz"));
headers.put("baz", "qux");
message = converter.toMessage(map);
assertEquals("foo", message.getPayload());
assertEquals("baz", message.getHeaders().get("bar"));
assertEquals("qux", message.getHeaders().get("baz"));
converter.setFilterHeadersInToMessage(true);
message = converter.toMessage(map);
assertEquals("foo", message.getPayload());
assertEquals("baz", message.getHeaders().get("bar"));
assertNull(message.getHeaders().get("baz"));
}
@Test
public void testInvalid() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo")
.setHeader("bar", "baz")
.setHeader("baz", "qux")
.build();
MapMessageConverter converter = new MapMessageConverter();
converter.setHeaderNames("bar");
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) converter.fromMessage(message);
map.remove("payload");
try {
converter.toMessage(map);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
assertEquals("'payload' entry cannot be null", e.getMessage());
}
}
@Test
public void testNoHeaders() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo")
.build();
MapMessageConverter converter = new MapMessageConverter();
converter.setHeaderNames("bar");
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) converter.fromMessage(message);
@SuppressWarnings("unchecked")
Map<String, Object> headers = (Map<String, Object>) map.get("headers");
assertNotNull(headers);
assertEquals(0, headers.size());
map.remove("headers");
message = converter.toMessage(map);
assertEquals("foo", message.getPayload());
}
@Test
public void testNotIncludedIfNull() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo")
.setHeader("bar", null)
.build();
MapMessageConverter converter = new MapMessageConverter();
converter.setHeaderNames("bar");
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) converter.fromMessage(message);
@SuppressWarnings("unchecked")
Map<String, Object> headers = (Map<String, Object>) map.get("headers");
assertNotNull(headers);
assertNotNull(map.get("payload"));
assertEquals("foo", map.get("payload"));
assertFalse(headers.keySet().contains("bar"));
assertEquals(0, headers.size());
}
}