/* * Copyright 2015 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.cloud.stream.module.tcp.sink; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import javax.net.ServerSocketFactory; import org.hamcrest.Matchers; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.context.ApplicationContext; import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory; import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory; import org.springframework.integration.ip.tcp.connection.TcpNioClientConnectionFactory; import org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer; import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer; import org.springframework.integration.ip.tcp.serializer.ByteArrayLengthHeaderSerializer; import org.springframework.integration.ip.tcp.serializer.ByteArrayLfSerializer; import org.springframework.integration.ip.tcp.serializer.ByteArrayRawSerializer; import org.springframework.integration.ip.tcp.serializer.ByteArraySingleTerminatorSerializer; import org.springframework.integration.ip.tcp.serializer.ByteArrayStxEtxSerializer; import org.springframework.integration.ip.tcp.serializer.SoftEndOfStreamException; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Tests for TcpSink. * * @author Gary Russell */ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = TcpSinkApplication.class) @DirtiesContext @WebIntegrationTest(randomPort = true, value = { "host = localhost", "port = ${tcp.sink.test.port}" }) public abstract class TcpSinkTests { private static TestTCPServer server; @Autowired protected Sink channels; @Autowired protected MessageCollector messageCollector; @Autowired protected AbstractClientConnectionFactory connectionFactory; @Autowired protected TcpSinkProperties properties; @Autowired protected ApplicationContext ctx; @BeforeClass public static void startup() { server = new TestTCPServer(); } @AfterClass public static void shutDown() { server.shutDown(); } @IntegrationTest({ "host = foo", "nio = true", "reverseLookup = true", "useDirectBuffers = true", "socketTimeout = 123", "close = true", "charset = bar" }) public static class PropertiesPopulatedTests extends TcpSinkTests { @Test public void test() throws Exception { assertThat(this.connectionFactory, Matchers.instanceOf(TcpNioClientConnectionFactory.class)); assertEquals("foo", this.connectionFactory.getHost()); assertTrue(TestUtils.getPropertyValue(this.connectionFactory, "lookupHost", Boolean.class)); assertTrue(TestUtils.getPropertyValue(this.connectionFactory, "usingDirectBuffers", Boolean.class)); assertEquals(123, TestUtils.getPropertyValue(this.connectionFactory, "soTimeout")); assertTrue(this.connectionFactory.isSingleUse()); assertEquals("bar", TestUtils.getPropertyValue(this.connectionFactory, "mapper.charset")); } } @IntegrationTest({ "host = foo" }) public static class NotNioTests extends TcpSinkTests { @Test public void test() throws Exception { assertThat(this.connectionFactory, Matchers.instanceOf(TcpNetClientConnectionFactory.class)); assertEquals("foo", this.connectionFactory.getHost()); assertFalse(TestUtils.getPropertyValue(this.connectionFactory, "lookupHost", Boolean.class)); assertEquals(120000, TestUtils.getPropertyValue(this.connectionFactory, "soTimeout")); } } public static class CRLFTests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArrayCrLfSerializer()); } } @IntegrationTest({ "encoder = LF" }) public static class LFTests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArrayLfSerializer()); } } @IntegrationTest({ "encoder = NULL" }) public static class NULLTests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArraySingleTerminatorSerializer((byte) 0)); } } @IntegrationTest({ "encoder = STXETX" }) public static class STXETXTests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArrayStxEtxSerializer()); } } @IntegrationTest({ "encoder = L1" }) public static class L1Tests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArrayLengthHeaderSerializer(1)); } } @IntegrationTest({ "encoder = L2" }) public static class L2Tests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArrayLengthHeaderSerializer(2)); } } @IntegrationTest({ "encoder = L4" }) public static class L4Tests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArrayLengthHeaderSerializer(4)); } } @IntegrationTest({ "encoder = RAW", "close = true" }) public static class RAWTests extends TcpSinkTests { @Test public void test() throws Exception { doTest(new ByteArrayRawSerializer()); } } /* * Sends two messages and asserts they arrive as expected on the other side using * the supplied decoder. */ protected void doTest(AbstractByteArraySerializer decoder) throws Exception { server.setDecoder(decoder); Message<String> message = new GenericMessage<>("foo"); assertTrue(channels.input().send(message)); String received = server.queue.poll(10, TimeUnit.SECONDS); assertEquals("foo", received); assertTrue(channels.input().send(message)); received = server.queue.poll(10, TimeUnit.SECONDS); assertEquals("foo", received); } /** * TCP server that uses the supplied {@link AbstractByteArraySerializer} * to decode the input stream and put the resulting message in a queue. * */ private static class TestTCPServer implements Runnable { private static final Logger logger = LoggerFactory.getLogger(TestTCPServer.class); private final ServerSocket serverSocket; private final ExecutorService executor; private volatile AbstractByteArraySerializer decoder; private final BlockingQueue<String> queue = new LinkedBlockingQueue<>(); private volatile boolean stopped; public TestTCPServer() { ServerSocket serverSocket = null; ExecutorService executor = null; try { serverSocket = ServerSocketFactory.getDefault().createServerSocket(0); System.setProperty("tcp.sink.test.port", Integer.toString(serverSocket.getLocalPort())); executor = Executors.newSingleThreadExecutor(); } catch (IOException e) { e.printStackTrace(); } this.serverSocket = serverSocket; this.executor = executor; this.decoder = new ByteArrayCrLfSerializer(); executor.execute(this); } private void setDecoder(AbstractByteArraySerializer decoder) { this.decoder = decoder; } @Override public void run() { while (true) { Socket socket = null; try { logger.info("Server listening on " + this.serverSocket.getLocalPort()); socket = this.serverSocket.accept(); while (true) { byte[] data = decoder.deserialize(socket.getInputStream()); queue.offer(new String(data)); } } catch (SoftEndOfStreamException e) { // normal close } catch (IOException e) { try { if (socket != null) { socket.close(); } } catch (IOException e1) { } logger.error(e.getMessage()); if (this.stopped) { logger.info("Server stopped on " + this.serverSocket.getLocalPort()); break; } } } } private void shutDown() { try { this.stopped = true; this.serverSocket.close(); this.executor.shutdownNow(); } catch (IOException e) { } } } }