/*
* Copyright 2002-2017 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.connection;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import java.io.OutputStream;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ServerSocketFactory;
import org.apache.commons.logging.Log;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.serializer.Serializer;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.ip.tcp.TcpInboundGateway;
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
import org.springframework.integration.ip.tcp.TcpSendingMessageHandler;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.SocketUtils;
/**
* @author Gary Russell
* @author Artyem Bilan
*
* @since 3.0
*
*/
public class ConnectionEventTests {
@Test
public void testConnectionEvents() throws Exception {
Socket socket = mock(Socket.class);
final List<TcpConnectionEvent> theEvent = new ArrayList<TcpConnectionEvent>();
TcpNetConnection conn = new TcpNetConnection(socket, false, false, new ApplicationEventPublisher() {
@Override
public void publishEvent(ApplicationEvent event) {
theEvent.add((TcpConnectionEvent) event);
}
@Override
public void publishEvent(Object event) {
}
}, "foo");
/*
* Open is not published by the connection itself; the factory publishes it after initialization.
* See ConnectionToConnectionTests.
*/
@SuppressWarnings("unchecked")
Serializer<Object> serializer = mock(Serializer.class);
RuntimeException toBeThrown = new RuntimeException("foo");
doThrow(toBeThrown).when(serializer).serialize(Mockito.any(Object.class), Mockito.any(OutputStream.class));
conn.setMapper(new TcpMessageMapper());
conn.setSerializer(serializer);
try {
conn.send(new GenericMessage<String>("bar"));
fail("Expected exception");
}
catch (Exception e) {
}
assertTrue(theEvent.size() > 0);
assertNotNull(theEvent.get(0));
assertTrue(theEvent.get(0) instanceof TcpConnectionExceptionEvent);
assertTrue(theEvent.get(0).toString().endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "]"));
assertThat(theEvent.get(0).toString(),
containsString("RuntimeException: foo, failedMessage=GenericMessage [payload=bar"));
TcpConnectionExceptionEvent event = (TcpConnectionExceptionEvent) theEvent.get(0);
assertNotNull(event.getCause());
assertSame(toBeThrown, event.getCause().getCause());
assertTrue(theEvent.size() > 1);
assertNotNull(theEvent.get(1));
assertTrue(theEvent.get(1).toString()
.endsWith("[factory=foo, connectionId=" + conn.getConnectionId() + "] **CLOSED**"));
}
@Test
public void testNetServerExceptionEvent() throws Exception {
int port = SocketUtils.findAvailableTcpPort();
AbstractServerConnectionFactory factory = new TcpNetServerConnectionFactory(port);
testServerExceptionGuts(port, factory);
}
@Test
public void testNioServerExceptionEvent() throws Exception {
int port = SocketUtils.findAvailableTcpPort();
AbstractServerConnectionFactory factory = new TcpNioServerConnectionFactory(port);
testServerExceptionGuts(port, factory);
}
@Test
public void testOutboundChannelAdapterNoConnectionEvents() {
TcpSendingMessageHandler handler = new TcpSendingMessageHandler();
AbstractServerConnectionFactory scf = new AbstractServerConnectionFactory(0) {
@Override
public void run() {
}
};
final AtomicReference<ApplicationEvent> theEvent = new AtomicReference<ApplicationEvent>();
scf.setApplicationEventPublisher(new ApplicationEventPublisher() {
@Override
public void publishEvent(Object event) {
}
@Override
public void publishEvent(ApplicationEvent event) {
theEvent.set(event);
}
});
handler.setConnectionFactory(scf);
handler.start();
Message<String> message = MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, "bar")
.build();
try {
handler.handleMessage(message);
fail("expected exception");
}
catch (MessageHandlingException e) {
assertThat(e.getMessage(), Matchers.containsString("Unable to find outbound socket"));
}
assertNotNull(theEvent.get());
TcpConnectionFailedCorrelationEvent event = (TcpConnectionFailedCorrelationEvent) theEvent.get();
assertEquals("bar", event.getConnectionId());
assertSame(message, ((MessagingException) event.getCause()).getFailedMessage());
}
@Test
public void testInboundGatewayNoConnectionEvents() {
TcpInboundGateway gw = new TcpInboundGateway();
AbstractServerConnectionFactory scf = new AbstractServerConnectionFactory(0) {
@Override
public void run() {
}
};
final AtomicReference<ApplicationEvent> theEvent = new AtomicReference<ApplicationEvent>();
scf.setApplicationEventPublisher(new ApplicationEventPublisher() {
@Override
public void publishEvent(Object event) {
}
@Override
public void publishEvent(ApplicationEvent event) {
theEvent.set(event);
}
});
gw.setConnectionFactory(scf);
DirectChannel requestChannel = new DirectChannel();
requestChannel.subscribe(message -> ((MessageChannel) message.getHeaders().getReplyChannel()).send(message));
gw.setRequestChannel(requestChannel);
gw.start();
Message<String> message = MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, "bar")
.build();
gw.onMessage(message);
assertNotNull(theEvent.get());
TcpConnectionFailedCorrelationEvent event = (TcpConnectionFailedCorrelationEvent) theEvent.get();
assertEquals("bar", event.getConnectionId());
assertSame(message, ((MessagingException) event.getCause()).getFailedMessage());
}
@Test
public void testOutboundGatewayNoConnectionEvents() {
TcpOutboundGateway gw = new TcpOutboundGateway();
AbstractClientConnectionFactory ccf = new AbstractClientConnectionFactory("localhost", 0) {
};
final AtomicReference<ApplicationEvent> theEvent = new AtomicReference<ApplicationEvent>();
ccf.setApplicationEventPublisher(new ApplicationEventPublisher() {
@Override
public void publishEvent(Object event) {
}
@Override
public void publishEvent(ApplicationEvent event) {
theEvent.set(event);
}
});
gw.setConnectionFactory(ccf);
DirectChannel requestChannel = new DirectChannel();
requestChannel.subscribe(message -> ((MessageChannel) message.getHeaders().getReplyChannel()).send(message));
gw.start();
Message<String> message = MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, "bar")
.build();
gw.onMessage(message);
assertNotNull(theEvent.get());
TcpConnectionFailedCorrelationEvent event = (TcpConnectionFailedCorrelationEvent) theEvent.get();
assertEquals("bar", event.getConnectionId());
MessagingException messagingException = (MessagingException) event.getCause();
assertSame(message, messagingException.getFailedMessage());
assertEquals("Cannot correlate response - no pending reply for bar", messagingException.getMessage());
message = new GenericMessage<String>("foo");
gw.onMessage(message);
assertNotNull(theEvent.get());
event = (TcpConnectionFailedCorrelationEvent) theEvent.get();
assertNull(event.getConnectionId());
messagingException = (MessagingException) event.getCause();
assertSame(message, messagingException.getFailedMessage());
assertEquals("Cannot correlate response - no connection id", messagingException.getMessage());
}
private void testServerExceptionGuts(int port, AbstractServerConnectionFactory factory) throws Exception {
ServerSocket ss = null;
try {
ss = ServerSocketFactory.getDefault().createServerSocket(port);
}
catch (Exception e) {
return; // skip this test, someone grabbed the port
}
final AtomicReference<TcpConnectionServerExceptionEvent> theEvent =
new AtomicReference<TcpConnectionServerExceptionEvent>();
final CountDownLatch latch = new CountDownLatch(1);
factory.setApplicationEventPublisher(new ApplicationEventPublisher() {
@Override
public void publishEvent(ApplicationEvent event) {
theEvent.set((TcpConnectionServerExceptionEvent) event);
latch.countDown();
}
@Override
public void publishEvent(Object event) {
}
});
factory.setBeanName("sf");
factory.registerListener(message -> false);
Log logger = spy(TestUtils.getPropertyValue(factory, "logger", Log.class));
doNothing().when(logger).error(anyString(), any(Throwable.class));
new DirectFieldAccessor(factory).setPropertyValue("logger", logger);
factory.start();
assertTrue(latch.await(10, TimeUnit.SECONDS));
String actual = theEvent.toString();
assertThat(actual, containsString("cause=java.net.BindException"));
assertThat(actual, containsString("source="
+ "sf, port=" + port));
ArgumentCaptor<String> reasonCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Throwable> throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
verify(logger).error(reasonCaptor.capture(), throwableCaptor.capture());
assertThat(reasonCaptor.getValue(), startsWith("Error on Server"));
assertThat(reasonCaptor.getValue(), endsWith("; port = " + port));
assertThat(throwableCaptor.getValue(), instanceOf(BindException.class));
ss.close();
}
@Test
public void testFailConnect() {
TcpNetClientConnectionFactory ccf = new TcpNetClientConnectionFactory("junkjunk", 1234);
final AtomicReference<ApplicationEvent> failEvent = new AtomicReference<ApplicationEvent>();
ccf.setApplicationEventPublisher(new ApplicationEventPublisher() {
@Override
public void publishEvent(Object event) {
}
@Override
public void publishEvent(ApplicationEvent event) {
failEvent.set(event);
}
});
ccf.start();
try {
ccf.getConnection();
fail("expected exception");
}
catch (Exception e) {
assertThat(e, instanceOf(UnknownHostException.class));
TcpConnectionFailedEvent event = (TcpConnectionFailedEvent) failEvent.get();
assertSame(e, event.getCause());
}
}
}