diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java
index 79d6041de4..e55cbc0a8d 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpAdapterParserUtils.java
@@ -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.
@@ -123,6 +123,8 @@ public abstract class IpAdapterParserUtils {
public static final String BACKLOG = "backlog";
+ public static final String MAPPER = "mapper";
+
private IpAdapterParserUtils() {}
/**
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpNamespaceHandler.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpNamespaceHandler.java
index f2b3600da1..52745dae02 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpNamespaceHandler.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/IpNamespaceHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 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.
@@ -20,7 +20,7 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa
/**
* Namespace handler for Spring Integration's ip namespace.
- *
+ *
* @author Gary Russell
* @since 2.0
*/
@@ -31,9 +31,10 @@ public class IpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
this.registerBeanDefinitionParser("udp-outbound-channel-adapter", new UdpOutboundChannelAdapterParser());
this.registerBeanDefinitionParser("tcp-inbound-gateway", new TcpInboundGatewayParser());
this.registerBeanDefinitionParser("tcp-outbound-gateway", new TcpOutboundGatewayParser());
- this.registerBeanDefinitionParser("tcp-connection-factory", new TcpConnectionParser());
+ this.registerBeanDefinitionParser("tcp-connection-factory", new TcpConnectionFactoryParser());
this.registerBeanDefinitionParser("tcp-inbound-channel-adapter", new TcpInboundChannelAdapterParser());
this.registerBeanDefinitionParser("tcp-outbound-channel-adapter", new TcpOutboundChannelAdapterParser());
+ this.registerBeanDefinitionParser("tcp-connection-event-inbound-channel-adapter", new TcpConnectionEventInboundChannelAdapterParser());
}
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionEventInboundChannelAdapterParser.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionEventInboundChannelAdapterParser.java
new file mode 100644
index 0000000000..4a92d4252e
--- /dev/null
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionEventInboundChannelAdapterParser.java
@@ -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.config;
+
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.integration.config.xml.AbstractChannelAdapterParser;
+import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.ip.tcp.connection.TcpConnectionEventListeningMessageProducer;
+import org.w3c.dom.Element;
+
+/**
+ * @author Gary Russell
+ * @since 3.0
+ */
+public class TcpConnectionEventInboundChannelAdapterParser extends AbstractChannelAdapterParser {
+
+ @Override
+ protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
+ BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder
+ .rootBeanDefinition(TcpConnectionEventListeningMessageProducer.class);
+ adapterBuilder.addPropertyReference("outputChannel", channelName);
+ IntegrationNamespaceUtils.setReferenceIfAttributeDefined(adapterBuilder, element, "error-channel", "errorChannel");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "event-types");
+ return adapterBuilder.getBeanDefinition();
+ }
+
+}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java
index 213b469c89..34308fde77 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpConnectionFactoryFactoryBean.java
@@ -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.
@@ -20,6 +20,8 @@ import java.util.concurrent.Executor;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.AbstractFactoryBean;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
@@ -50,7 +52,8 @@ import org.springframework.util.Assert;
* @author Gary Russell
* @since 2.0.5
*/
-public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean implements SmartLifecycle, BeanNameAware {
+public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean implements SmartLifecycle, BeanNameAware,
+ ApplicationEventPublisherAware {
private volatile AbstractConnectionFactory connectionFactory;
@@ -108,6 +111,8 @@ public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean getObjectType() {
return this.connectionFactory != null ? this.connectionFactory.getClass()
@@ -168,6 +173,7 @@ public class TcpConnectionFactoryFactoryBean extends AbstractFactoryBean message) {
TcpConnection connection = null;
Assert.notNull(this.clientConnectionFactory, "'clientConnectionFactory' cannot be null");
@@ -334,15 +315,6 @@ public class TcpSendingMessageHandler extends AbstractMessageHandler implements
this.isClientMode = isClientMode;
}
- /**
- * @param scheduler the scheduler to set
- * @deprecated Use {@link #setTaskScheduler(TaskScheduler)}
- */
- @Deprecated
- public void setScheduler(TaskScheduler scheduler) {
- this.setTaskScheduler(scheduler);
- }
-
@Override // super class is protected
public void setTaskScheduler(TaskScheduler taskScheduler) {
super.setTaskScheduler(taskScheduler);
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java
index d48434dace..54bef29fe4 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java
@@ -19,11 +19,14 @@ package org.springframework.integration.ip.tcp.connection;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
+import java.net.SocketTimeoutException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -35,6 +38,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
@@ -51,7 +56,7 @@ import org.springframework.util.Assert;
*
*/
public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
- implements ConnectionFactory, SmartLifecycle {
+ implements ConnectionFactory, SmartLifecycle, ApplicationEventPublisherAware {
protected static final int DEFAULT_REPLY_TIMEOUT = 10000;
@@ -95,7 +100,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
private volatile boolean lookupHost = true;
- private volatile List connections = new LinkedList();
+ private volatile List connections = new LinkedList();
private volatile TcpSocketSupport tcpSocketSupport = new DefaultTcpSocketSupport();
@@ -105,6 +110,8 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
private volatile int nioHarvestInterval = DEFAULT_NIO_HARVEST_INTERVAL;
+ private volatile ApplicationEventPublisher applicationEventPublisher;
+
private static final int DEFAULT_NIO_HARVEST_INTERVAL = 2000;
public AbstractConnectionFactory(int port) {
@@ -117,6 +124,14 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
this.port = port;
}
+ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+ this.applicationEventPublisher = applicationEventPublisher;
+ }
+
+ protected ApplicationEventPublisher getApplicationEventPublisher() {
+ return applicationEventPublisher;
+ }
+
/**
* Sets socket attributes on the socket.
* @param socket The socket.
@@ -290,17 +305,6 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
return mapper;
}
- /**
- * @deprecated This property is no longer used. If you wish
- * to use a fixed thread pool, provide your own Executor
- * in {@link #setTaskExecutor(Executor)}.
- * @return the poolSize
- */
- @Deprecated
- public int getPoolSize() {
- return 0;
- }
-
/**
* Registers a TcpListener to receive messages after
* the payload has been converted from the input data.
@@ -371,16 +375,6 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
}
- /**
- * @deprecated Default task executor is now a cached rather
- * than a fixed pool executor. To use a pool, supply an
- * appropriate Executor in {@link AbstractConnectionFactory#setTaskExecutor(Executor)}.
- * Use {@link AbstractServerConnectionFactory#setBacklog(int)} to set the connection backlog.
- */
- @Deprecated
- public void setPoolSize(int poolSize) {
- }
-
public void setInterceptorFactoryChain(TcpConnectionInterceptorFactoryChain interceptorFactoryChain) {
this.interceptorFactoryChain = interceptorFactoryChain;
}
@@ -447,7 +441,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
this.active = false;
this.close();
synchronized (this.connections) {
- Iterator iterator = this.connections.iterator();
+ Iterator iterator = this.connections.iterator();
while (iterator.hasNext()) {
TcpConnection connection = iterator.next();
connection.close();
@@ -554,6 +548,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
this.port + " : " +
connection.getConnectionId());
}
+ connection.publishConnectionExceptionEvent(new SocketTimeoutException("Timing out connection"));
connection.timeout();
}
}
@@ -650,7 +645,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
callback.run();
}
- protected void addConnection(TcpConnection connection) {
+ protected void addConnection(TcpConnectionSupport connection) {
synchronized (this.connections) {
if (!this.active) {
connection.close();
@@ -660,18 +655,34 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
}
}
- protected void harvestClosedConnections() {
+ /**
+ * Cleans up this.connections by removing any closed connections.
+ * @return a list of open connection ids.
+ */
+ private List removeClosedConnectionsAndReturnOpenConnectionIds() {
synchronized (this.connections) {
- Iterator iterator = this.connections.iterator();
+ List openConnectionIds = new ArrayList();
+ Iterator iterator = this.connections.iterator();
while (iterator.hasNext()) {
TcpConnection connection = iterator.next();
if (!connection.isOpen()) {
iterator.remove();
}
+ else {
+ openConnectionIds.add(connection.getConnectionId());
+ }
}
+ return openConnectionIds;
}
}
+ /**
+ * Cleans up this.connections by removing any closed connections.
+ */
+ protected void harvestClosedConnections() {
+ this.removeClosedConnectionsAndReturnOpenConnectionIds();
+ }
+
public boolean isRunning() {
return this.active;
}
@@ -705,4 +716,40 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
this.tcpSocketSupport = tcpSocketSupport;
}
+ /**
+ * Returns a list of (currently) open {@link TcpConnection} connection ids; allows,
+ * for example, broadcast operations to all open connections.
+ * @return the list of connection ids.
+ */
+ public List getOpenConnectionIds() {
+ return Collections.unmodifiableList(this.removeClosedConnectionsAndReturnOpenConnectionIds());
+ }
+
+ /**
+ * Close a connection with the specified connection id.
+ * @param connectionId the connection id.
+ * @return true if the connection was closed.
+ */
+ public boolean closeConnection(String connectionId) {
+ Assert.notNull(connectionId, "'connectionId' to close must not be null");
+ synchronized(this.connections) {
+ boolean closed = false;
+ for (TcpConnectionSupport connection : connections) {
+ if (connectionId.equals(connection.getConnectionId())) {
+ try {
+ connection.close();
+ closed = true;
+ break;
+ }
+ catch (Exception e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to close connection " + connectionId, e);
+ }
+ connection.publishConnectionExceptionEvent(e);
+ }
+ }
+ }
+ return closed;
+ }
+ }
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java
index 2fbb807acf..6699b18b63 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractServerConnectionFactory.java
@@ -165,17 +165,6 @@ public abstract class AbstractServerConnectionFactory
this.backlog = backlog;
}
- /**
- * @deprecated Default task executor is now a cached rather
- * than a fixed pool executor.
- * Use {@link #setBacklog(int)} to set the connection backlog.
- */
- @Override
- @Deprecated
- public void setPoolSize(int poolSize) {
- this.setBacklog(poolSize);
- }
-
public int beforeShutdown() {
this.shuttingDown = true;
return 0;
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/CachingClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/CachingClientConnectionFactory.java
index 6996ed00a2..701265fc64 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/CachingClientConnectionFactory.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/CachingClientConnectionFactory.java
@@ -71,12 +71,10 @@ public class CachingClientConnectionFactory extends AbstractClientConnectionFact
this.pool.setWaitTimeout(connectionWaitTimeout);
}
- @Override
public synchronized void setPoolSize(int poolSize) {
this.pool.setPoolSize(poolSize);
}
- @Override
public int getPoolSize() {
return this.pool.getPoolSize();
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioConnectionSupport.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioConnectionSupport.java
index 9d8ad36be4..35e70ced0d 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioConnectionSupport.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioConnectionSupport.java
@@ -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,6 +17,8 @@ package org.springframework.integration.ip.tcp.connection;
import java.nio.channels.SocketChannel;
+import org.springframework.context.ApplicationEventPublisher;
+
/**
* Implementation of {@link TcpNioConnectionSupport} for non-SSL
@@ -27,9 +29,9 @@ import java.nio.channels.SocketChannel;
*/
public class DefaultTcpNioConnectionSupport implements TcpNioConnectionSupport {
- public TcpNioConnection createNewConnection(SocketChannel socketChannel,
- boolean server, boolean lookupHost) throws Exception {
- return new TcpNioConnection(socketChannel, server, lookupHost);
+ public TcpNioConnection createNewConnection(SocketChannel socketChannel, boolean server, boolean lookupHost,
+ ApplicationEventPublisher applicationEventPublisher, String connectionFactoryName) throws Exception {
+ return new TcpNioConnection(socketChannel, server, lookupHost, applicationEventPublisher, connectionFactoryName);
}
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioSSLConnectionSupport.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioSSLConnectionSupport.java
index 4ffdfcb60d..d1b6993c6d 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioSSLConnectionSupport.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/DefaultTcpNioSSLConnectionSupport.java
@@ -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.
@@ -21,6 +21,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.Assert;
/**
@@ -41,11 +42,14 @@ public class DefaultTcpNioSSLConnectionSupport implements TcpNioConnectionSuppor
this.sslContextSupport = sslContextSupport;
}
- public TcpNioConnection createNewConnection(SocketChannel socketChannel,
- boolean server, boolean lookupHost) throws Exception {
-
+ /**
+ * Creates a {@link TcpNioSSLConnection}.
+ */
+ public TcpNioConnection createNewConnection(SocketChannel socketChannel, boolean server, boolean lookupHost,
+ ApplicationEventPublisher applicationEventPublisher, String connectionFactoryName) throws Exception {
SSLEngine sslEngine = this.sslContext.createSSLEngine();
- TcpNioSSLConnection tcpNioSSLConnection = new TcpNioSSLConnection(socketChannel, server, lookupHost, sslEngine);
+ TcpNioSSLConnection tcpNioSSLConnection = new TcpNioSSLConnection(socketChannel, server, lookupHost,
+ applicationEventPublisher, connectionFactoryName, sslEngine);
tcpNioSSLConnection.init();
return tcpNioSSLConnection;
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/FailoverClientConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/FailoverClientConnectionFactory.java
index b5c326b8ed..1e71cc7878 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/FailoverClientConnectionFactory.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/FailoverClientConnectionFactory.java
@@ -20,8 +20,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.UUID;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
import org.springframework.integration.Message;
@@ -39,8 +37,6 @@ import org.springframework.util.Assert;
*/
public class FailoverClientConnectionFactory extends AbstractClientConnectionFactory {
- private static final Log logger = LogFactory.getLog(FailoverClientConnectionFactory.class);
-
private final List factories;
public FailoverClientConnectionFactory(List factories) {
@@ -279,6 +275,10 @@ public class FailoverClientConnectionFactory extends AbstractClientConnectionFac
return this.delegate.getPort();
}
+ public Object getDeserializerStateKey() {
+ return this.delegate.getDeserializerStateKey();
+ }
+
@Override
public void registerListener(TcpListener listener) {
this.delegate.registerListener(listener);
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java
index 427104851f..9311ca7237 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java
@@ -114,4 +114,12 @@ public interface TcpConnection extends Runnable {
*/
long incrementAndGetConnectionSequence();
+ /**
+ * @return a key that can be used to reference state in a {@link Deserializer} that
+ * maintains state for this connection. Currently, this would be the InputStream
+ * associated with the connection, but the object should be treated as opaque
+ * and ONLY used as a key.
+ */
+ Object getDeserializerStateKey();
+
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionEvent.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionEvent.java
new file mode 100644
index 0000000000..96dfe5319d
--- /dev/null
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionEvent.java
@@ -0,0 +1,91 @@
+/*
+ * 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.connection;
+
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * ApplicationEvent representing certain operations on a {@link TcpConnection}.
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public class TcpConnectionEvent extends ApplicationEvent {
+
+ private static final long serialVersionUID = 5323436446362192129L;
+
+ /**
+ * Valid EventTypes - subclasses should provide similar enums for
+ * their types.
+ *
+ */
+ public enum TcpConnectionEventType implements EventType {
+ OPEN,
+ CLOSE,
+ EXCEPTION;
+ }
+
+ private final EventType type;
+
+ private final String connectionFactoryName;
+
+ private final Throwable throwable;
+
+ public TcpConnectionEvent(TcpConnectionSupport connection, EventType type,
+ String connectionFactoryName) {
+ super(connection);
+ this.type = type;
+ this.throwable = null;
+ this.connectionFactoryName = connectionFactoryName;
+ }
+
+ public TcpConnectionEvent(TcpConnectionSupport connection, Throwable t,
+ String connectionFactoryName) {
+ super(connection);
+ this.type = TcpConnectionEventType.EXCEPTION;
+ this.throwable = t;
+ this.connectionFactoryName = connectionFactoryName;
+ }
+
+ public EventType getType() {
+ return type;
+ }
+
+ public String getConnectionId() {
+ return ((TcpConnection) this.getSource()).getConnectionId();
+ }
+
+ public String getConnectionFactoryName() {
+ return connectionFactoryName;
+ }
+
+ @Override
+ public String toString() {
+ return "TcpConnectionEvent [type=" + this.getType() +
+ ", factory=" + this.connectionFactoryName +
+ ", connectionId=" + this.getConnectionId() +
+ (this.throwable == null ? "" : ", Exception=" + this.throwable) + "]";
+ }
+
+ /**
+ * A marker interface allowing the definition of enums for allowed event types.
+ *
+ */
+ public interface EventType {
+
+ }
+
+}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionEventListeningMessageProducer.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionEventListeningMessageProducer.java
new file mode 100644
index 0000000000..02c6e54d5b
--- /dev/null
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionEventListeningMessageProducer.java
@@ -0,0 +1,73 @@
+/*
+ * 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.connection;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.context.ApplicationListener;
+import org.springframework.integration.Message;
+import org.springframework.integration.core.MessageProducer;
+import org.springframework.integration.endpoint.MessageProducerSupport;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * {@link MessageProducer} that produces Messages with @link {@link TcpConnectionEvent}
+ * payloads.
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public class TcpConnectionEventListeningMessageProducer extends MessageProducerSupport
+ implements ApplicationListener {
+
+ private volatile Set> eventTypes =
+ new HashSet>();
+
+ /**
+ * Set the list of event types (classes that extend TcpConnectionEvent) that
+ * this adapter should send to the message channel. By default, all event
+ * types will be sent.
+ */
+ @SuppressWarnings("unchecked")
+ public void setEventTypes(Class extends TcpConnectionEvent>[] eventTypes) {
+ Assert.notEmpty(eventTypes, "at least one event type is required");
+ Set> eventTypeSet = new HashSet>();
+ eventTypeSet.addAll(CollectionUtils.arrayToList(eventTypes));
+ this.eventTypes = eventTypeSet;
+ }
+
+ public void onApplicationEvent(TcpConnectionEvent event) {
+ if (CollectionUtils.isEmpty(this.eventTypes)) {
+ this.sendMessage(messageFromEvent(event));
+ }
+ else {
+ for (Class extends TcpConnectionEvent> eventType : this.eventTypes) {
+ if (eventType.isAssignableFrom(event.getClass())) {
+ this.sendMessage(messageFromEvent(event));
+ break;
+ }
+ }
+ }
+ }
+
+ protected Message messageFromEvent(TcpConnectionEvent event) {
+ return MessageBuilder.withPayload(event).build();
+ }
+
+}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorSupport.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorSupport.java
index a0e4456473..3a988fa4cc 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorSupport.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorSupport.java
@@ -64,6 +64,10 @@ public abstract class TcpConnectionInterceptorSupport extends TcpConnectionSuppo
return this.theConnection.getPort();
}
+ public Object getDeserializerStateKey() {
+ return this.theConnection.getDeserializerStateKey();
+ }
+
@Override
public void registerListener(TcpListener listener) {
this.tcpListener = listener;
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java
index 7022c07432..1d2ec18560 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionSupport.java
@@ -20,13 +20,16 @@ import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
import org.springframework.integration.Message;
+import org.springframework.integration.ip.tcp.connection.TcpConnectionEvent.TcpConnectionEventType;
import org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer;
import org.springframework.util.Assert;
@@ -71,11 +74,32 @@ public abstract class TcpConnectionSupport implements TcpConnection {
private volatile String hostAddress = "unknown";
+ private volatile String connectionFactoryName = "unknown";
+
+ private final ApplicationEventPublisher applicationEventPublisher;
+
+ private final AtomicBoolean closePublished = new AtomicBoolean();
+
public TcpConnectionSupport() {
- server = false;
+ this.server = false;
+ this.applicationEventPublisher = null;
}
- public TcpConnectionSupport(Socket socket, boolean server, boolean lookupHost) {
+ /**
+ * Creates a TcpConnectionSupport object and publishes a {@link TcpConnectionEvent#OPEN}
+ * event, if so configured.
+ * @param socket the underlying socket.
+ * @param server true if this connection is a server connection
+ * @param lookupHost true if reverse lookup of the host name should be performed,
+ * otherwise, the ip address will be used for identification purposes.
+ * @param applicationEventPublisher the publisher to which OPEN, CLOSE and EXCEPTION events will
+ * be sent; may be null if event publishing is not required.
+ * @param connectionFactoryName the name of the connection factory creating this connection; used
+ * during event publishing, may be null, in which case "unknown" will be used.
+ */
+ public TcpConnectionSupport(Socket socket, boolean server, boolean lookupHost,
+ ApplicationEventPublisher applicationEventPublisher,
+ String connectionFactoryName) {
this.server = server;
InetAddress inetAddress = socket.getInetAddress();
if (inetAddress != null) {
@@ -91,6 +115,11 @@ public abstract class TcpConnectionSupport implements TcpConnection {
try {
this.soLinger = socket.getSoLinger();
} catch (SocketException e) { }
+ this.applicationEventPublisher = applicationEventPublisher;
+ if (connectionFactoryName != null) {
+ this.connectionFactoryName = connectionFactoryName;
+ }
+ this.publishConnectionOpenEvent();
}
public void afterSend(Message> message) throws Exception {
@@ -115,6 +144,10 @@ public abstract class TcpConnectionSupport implements TcpConnection {
if (this.sender != null) {
this.sender.removeDeadConnection(this);
}
+ // close() may be called multiple times; only publish once
+ if (!this.closePublished.getAndSet(true)) {
+ this.publishConnectionCloseEvent();
+ }
}
/**
@@ -266,4 +299,51 @@ public abstract class TcpConnectionSupport implements TcpConnection {
return this.connectionId;
}
+ protected void publishConnectionOpenEvent() {
+ TcpConnectionEvent event = new TcpConnectionEvent(this, TcpConnectionEventType.OPEN,
+ this.connectionFactoryName);
+ doPublish(event);
+ }
+
+ protected void publishConnectionCloseEvent() {
+ TcpConnectionEvent event = new TcpConnectionEvent(this, TcpConnectionEventType.CLOSE,
+ this.connectionFactoryName);
+ doPublish(event);
+ }
+
+ protected void publishConnectionExceptionEvent(Throwable t) {
+ TcpConnectionEvent event = new TcpConnectionEvent(this, t,
+ this.connectionFactoryName);
+ doPublish(event);
+ }
+
+ /**
+ * Allow interceptors etc to publish events, perhaps subclasses of
+ * TcpConnectionEvent. The event source must be this connection.
+ * @param event the event to publish.
+ */
+ public void publishEvent(TcpConnectionEvent event) {
+ Assert.isTrue(event.getSource() == this, "Can only publish events with this as the source");
+ this.doPublish(event);
+ }
+
+ private void doPublish(TcpConnectionEvent event) {
+ try {
+ if (this.applicationEventPublisher == null) {
+ logger.warn("No publisher available to publish " + event);
+ }
+ else {
+ this.applicationEventPublisher.publishEvent(event);
+ }
+ }
+ catch (Exception e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to publish " + event, e);
+ }
+ else if (logger.isWarnEnabled()) {
+ logger.warn("Failed to publish " + event + ":" + e.getMessage());
+ }
+ }
+ }
+
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpMessageMapper.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpMessageMapper.java
index 1456aed85f..4deae52ed6 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpMessageMapper.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpMessageMapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 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.
@@ -16,6 +16,7 @@
package org.springframework.integration.ip.tcp.connection;
import java.io.UnsupportedEncodingException;
+import java.util.Map;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHandlingException;
@@ -31,7 +32,9 @@ import org.springframework.integration.support.MessageBuilder;
* charset (UTF-8 by default).
* Inbound messages include headers representing the remote end of the
* connection as well as a connection id that can be used by a {@link TcpSender}
- * to correlate which connection to send a reply.
+ * to correlate which connection to send a reply. If applySequence is set, adds
+ * standard correlationId/sequenceNumber headers allowing for downstream (unbounded)
+ * resequencing.
* @author Gary Russell
* @since 2.0
*
@@ -50,27 +53,35 @@ public class TcpMessageMapper implements
Message