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:
committed by
Gary Russell
parent
1847eaa194
commit
0699fdc6cf
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
@@ -27,9 +29,13 @@ import java.util.Map;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.serializer.DefaultDeserializer;
|
||||
import org.springframework.core.serializer.DefaultSerializer;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.ip.IpHeaders;
|
||||
import org.springframework.integration.ip.tcp.serializer.MapJsonSerializer;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.support.converter.MapMessageConverter;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
@@ -207,4 +213,72 @@ public class TcpMessageMapperTests {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
public void testMapMessageConvertingOutboundJson() throws Exception {
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader("bar", "baz")
|
||||
.build();
|
||||
MapMessageConverter converter = new MapMessageConverter();
|
||||
converter.setHeaderNames("bar");
|
||||
MessageConvertingTcpMessageMapper mapper = new MessageConvertingTcpMessageMapper(converter);
|
||||
Map<?, ?> map = (Map<?, ?>) mapper.fromMessage(message);
|
||||
MapJsonSerializer serializer = new MapJsonSerializer();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
serializer.serialize(map, baos);
|
||||
assertEquals("{\"headers\":{\"bar\":\"baz\"},\"payload\":\"foo\"}\n", new String(baos.toByteArray(), "UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapMessageConvertingInboundJson() throws Exception {
|
||||
String json = "{\"headers\":{\"bar\":\"baz\"},\"payload\":\"foo\"}\n";
|
||||
MapMessageConverter converter = new MapMessageConverter();
|
||||
MessageConvertingTcpMessageMapper mapper = new MessageConvertingTcpMessageMapper(converter);
|
||||
MapJsonSerializer deserializer = new MapJsonSerializer();
|
||||
Map<?, ?> map = deserializer.deserialize(new ByteArrayInputStream(json.getBytes("UTF-8")));
|
||||
|
||||
TcpConnection connection = mock(TcpConnection.class);
|
||||
when(connection.getPayload()).thenReturn(map);
|
||||
when(connection.getHostName()).thenReturn("someHost");
|
||||
when(connection.getHostAddress()).thenReturn("1.1.1.1");
|
||||
when(connection.getPort()).thenReturn(1234);
|
||||
when(connection.getConnectionId()).thenReturn("someId");
|
||||
Message<?> message = mapper.toMessage(connection);
|
||||
assertEquals("foo", message.getPayload());
|
||||
assertEquals("baz", message.getHeaders().get("bar"));
|
||||
assertEquals("someHost", message.getHeaders().get(IpHeaders.HOSTNAME));
|
||||
assertEquals("1.1.1.1", message.getHeaders().get(IpHeaders.IP_ADDRESS));
|
||||
assertEquals(1234, message.getHeaders().get(IpHeaders.REMOTE_PORT));
|
||||
assertEquals("someId", message.getHeaders().get(IpHeaders.CONNECTION_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapMessageConvertingBothWaysJava() throws Exception {
|
||||
Message<String> outMessage = MessageBuilder.withPayload("foo")
|
||||
.setHeader("bar", "baz")
|
||||
.build();
|
||||
MapMessageConverter converter = new MapMessageConverter();
|
||||
converter.setHeaderNames("bar");
|
||||
MessageConvertingTcpMessageMapper mapper = new MessageConvertingTcpMessageMapper(converter);
|
||||
Map<?, ?> map = (Map<?, ?>) mapper.fromMessage(outMessage);
|
||||
DefaultSerializer serializer = new DefaultSerializer();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
serializer.serialize(map, baos);
|
||||
|
||||
DefaultDeserializer deserializer = new DefaultDeserializer();
|
||||
map = (Map<?, ?>) deserializer.deserialize(new ByteArrayInputStream(baos.toByteArray()));
|
||||
TcpConnection connection = mock(TcpConnection.class);
|
||||
when(connection.getPayload()).thenReturn(map);
|
||||
when(connection.getHostName()).thenReturn("someHost");
|
||||
when(connection.getHostAddress()).thenReturn("1.1.1.1");
|
||||
when(connection.getPort()).thenReturn(1234);
|
||||
when(connection.getConnectionId()).thenReturn("someId");
|
||||
Message<?> message = mapper.toMessage(connection);
|
||||
assertEquals("foo", message.getPayload());
|
||||
assertEquals("baz", message.getHeaders().get("bar"));
|
||||
assertEquals("someHost", message.getHeaders().get(IpHeaders.HOSTNAME));
|
||||
assertEquals("1.1.1.1", message.getHeaders().get(IpHeaders.IP_ADDRESS));
|
||||
assertEquals(1234, message.getHeaders().get(IpHeaders.REMOTE_PORT));
|
||||
assertEquals("someId", message.getHeaders().get(IpHeaders.CONNECTION_ID));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,19 +21,30 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNioConnection.ChannelInputStream;
|
||||
import org.springframework.integration.ip.tcp.serializer.ByteArrayStxEtxSerializer;
|
||||
import org.springframework.integration.ip.tcp.serializer.MapJsonSerializer;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.support.converter.MapMessageConverter;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
/**
|
||||
@@ -43,13 +54,18 @@ import org.springframework.integration.test.util.TestUtils;
|
||||
*/
|
||||
public class TcpNetConnectionTests {
|
||||
|
||||
private final ApplicationEventPublisher nullPublisher = new ApplicationEventPublisher() {
|
||||
public void publishEvent(ApplicationEvent event) {
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testErrorLog() throws Exception {
|
||||
Socket socket = mock(Socket.class);
|
||||
InputStream stream = mock(InputStream.class);
|
||||
when(socket.getInputStream()).thenReturn(stream);
|
||||
when(stream.read()).thenReturn((int) 'x');
|
||||
TcpNetConnection connection = new TcpNetConnection(socket, true, false, null, null);
|
||||
TcpNetConnection connection = new TcpNetConnection(socket, true, false, nullPublisher, null);
|
||||
connection.setDeserializer(new ByteArrayStxEtxSerializer());
|
||||
final AtomicReference<Object> log = new AtomicReference<Object>();
|
||||
Log logger = mock(Log.class);
|
||||
@@ -76,10 +92,55 @@ public class TcpNetConnectionTests {
|
||||
SocketChannel socketChannel = mock(SocketChannel.class);
|
||||
Socket socket = mock(Socket.class);
|
||||
when(socketChannel.socket()).thenReturn(socket);
|
||||
TcpNioConnection connection = new TcpNioConnection(socketChannel, true, false, null, null);
|
||||
TcpNioConnection connection = new TcpNioConnection(socketChannel, true, false, nullPublisher, null);
|
||||
ChannelInputStream inputStream = TestUtils.getPropertyValue(connection, "channelInputStream", ChannelInputStream.class);
|
||||
inputStream.write(new byte[] {(byte) 0x80}, 1);
|
||||
assertEquals(0x80, inputStream.read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transferHeaders() throws Exception {
|
||||
Socket inSocket = mock(Socket.class);
|
||||
PipedInputStream pipe = new PipedInputStream();
|
||||
when(inSocket.getInputStream()).thenReturn(pipe);
|
||||
|
||||
TcpConnectionSupport inboundConnection = new TcpNetConnection(inSocket, true, false, nullPublisher, null);
|
||||
inboundConnection.setDeserializer(new MapJsonSerializer());
|
||||
MapMessageConverter inConverter = new MapMessageConverter();
|
||||
MessageConvertingTcpMessageMapper inMapper = new MessageConvertingTcpMessageMapper(inConverter);
|
||||
inboundConnection.setMapper(inMapper);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Socket outSocket = mock(Socket.class);
|
||||
TcpNetConnection outboundConnection = new TcpNetConnection(outSocket, true, false, nullPublisher, null);
|
||||
when(outSocket.getOutputStream()).thenReturn(baos);
|
||||
|
||||
MapMessageConverter outConverter = new MapMessageConverter();
|
||||
outConverter.setHeaderNames("bar");
|
||||
MessageConvertingTcpMessageMapper outMapper = new MessageConvertingTcpMessageMapper(outConverter);
|
||||
outboundConnection.setMapper(outMapper);
|
||||
outboundConnection.setSerializer(new MapJsonSerializer());
|
||||
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader("bar", "baz")
|
||||
.build();
|
||||
outboundConnection.send(message);
|
||||
PipedOutputStream out = new PipedOutputStream(pipe);
|
||||
out.write(baos.toByteArray());
|
||||
out.close();
|
||||
|
||||
final AtomicReference<Message<?>> inboundMessage = new AtomicReference<Message<?>>();
|
||||
TcpListener listener = new TcpListener() {
|
||||
|
||||
public boolean onMessage(Message<?> message) {
|
||||
inboundMessage.set(message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
inboundConnection.registerListener(listener);
|
||||
inboundConnection.run();
|
||||
assertNotNull(inboundMessage.get());
|
||||
assertEquals("foo", inboundMessage.get().getPayload());
|
||||
assertEquals("baz", inboundMessage.get().getHeaders().get("bar"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* 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.
|
||||
@@ -17,12 +17,15 @@
|
||||
package org.springframework.integration.ip.tcp.connection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -46,18 +49,23 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.net.ServerSocketFactory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.ip.tcp.connection.TcpNioConnection.ChannelInputStream;
|
||||
import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
|
||||
import org.springframework.integration.ip.tcp.serializer.MapJsonSerializer;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.support.converter.MapMessageConverter;
|
||||
import org.springframework.integration.test.util.SocketUtils;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -72,6 +80,11 @@ import org.springframework.util.ReflectionUtils.FieldFilter;
|
||||
*/
|
||||
public class TcpNioConnectionTests {
|
||||
|
||||
private final ApplicationEventPublisher nullPublisher = new ApplicationEventPublisher() {
|
||||
public void publishEvent(ApplicationEvent event) {
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testWriteTimeout() throws Exception {
|
||||
final int port = SocketUtils.findAvailableServerSocket();
|
||||
@@ -442,6 +455,70 @@ public class TcpNioConnectionTests {
|
||||
assertEquals("foo\u0000", new String(out));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transferHeaders() throws Exception {
|
||||
Socket inSocket = mock(Socket.class);
|
||||
SocketChannel inChannel = mock(SocketChannel.class);
|
||||
when(inChannel.socket()).thenReturn(inSocket);
|
||||
|
||||
TcpNioConnection inboundConnection = new TcpNioConnection(inChannel, true, false, nullPublisher, null);
|
||||
inboundConnection.setDeserializer(new MapJsonSerializer());
|
||||
MapMessageConverter inConverter = new MapMessageConverter();
|
||||
MessageConvertingTcpMessageMapper inMapper = new MessageConvertingTcpMessageMapper(inConverter);
|
||||
inboundConnection.setMapper(inMapper);
|
||||
final ByteArrayOutputStream written = new ByteArrayOutputStream();
|
||||
doAnswer(new Answer<Integer>() {
|
||||
public Integer answer(InvocationOnMock invocation) throws Throwable {
|
||||
ByteBuffer buff = (ByteBuffer) invocation.getArguments()[0];
|
||||
byte[] bytes = written.toByteArray();
|
||||
buff.put(bytes);
|
||||
return bytes.length;
|
||||
}
|
||||
}).when(inChannel).read(any(ByteBuffer.class));
|
||||
|
||||
Socket outSocket = mock(Socket.class);
|
||||
SocketChannel outChannel = mock(SocketChannel.class);
|
||||
when(outChannel.socket()).thenReturn(outSocket);
|
||||
TcpNioConnection outboundConnection = new TcpNioConnection(outChannel, true, false, nullPublisher, null);
|
||||
doAnswer(new Answer<Object>() {
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
ByteBuffer buff = (ByteBuffer) invocation.getArguments()[0];
|
||||
byte[] bytes = new byte[buff.limit()];
|
||||
buff.get(bytes);
|
||||
written.write(bytes);
|
||||
return null;
|
||||
}
|
||||
}).when(outChannel).write(any(ByteBuffer.class));
|
||||
|
||||
MapMessageConverter outConverter = new MapMessageConverter();
|
||||
outConverter.setHeaderNames("bar");
|
||||
MessageConvertingTcpMessageMapper outMapper = new MessageConvertingTcpMessageMapper(outConverter);
|
||||
outboundConnection.setMapper(outMapper);
|
||||
outboundConnection.setSerializer(new MapJsonSerializer());
|
||||
|
||||
Message<String> message = MessageBuilder.withPayload("foo")
|
||||
.setHeader("bar", "baz")
|
||||
.build();
|
||||
outboundConnection.send(message);
|
||||
|
||||
final AtomicReference<Message<?>> inboundMessage = new AtomicReference<Message<?>>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
TcpListener listener = new TcpListener() {
|
||||
|
||||
public boolean onMessage(Message<?> message) {
|
||||
inboundMessage.set(message);
|
||||
latch.countDown();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
inboundConnection.registerListener(listener);
|
||||
inboundConnection.readPacket();
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
assertNotNull(inboundMessage.get());
|
||||
assertEquals("foo", inboundMessage.get().getPayload());
|
||||
assertEquals("baz", inboundMessage.get().getHeaders().get("bar"));
|
||||
}
|
||||
|
||||
private void readFully(InputStream is, byte[] buff) throws IOException {
|
||||
for (int i = 0; i < buff.length; i++) {
|
||||
buff[i] = (byte) is.read();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.ip.tcp.serializer;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public class MapJsonSerializerTests {
|
||||
|
||||
@Test
|
||||
public void multi() throws Exception {
|
||||
String json = "{\"headers\":{\"bar\":\"baz\"},\"payload\":\"foo\"}\n";
|
||||
String twoJson = json + json;
|
||||
MapJsonSerializer deserializer = new MapJsonSerializer();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(twoJson.getBytes("UTF-8"));
|
||||
Map<?, ?> map = deserializer.deserialize(bais);
|
||||
assertNotNull(map);
|
||||
map = deserializer.deserialize(bais);
|
||||
assertNotNull(map);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user