/*
* Copyright 2002-2016 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.net.Socket;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.springframework.context.ApplicationEventPublisher;
/**
* Abstract class for client connection factories; client connection factories
* establish outgoing connections.
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*
*/
public abstract class AbstractClientConnectionFactory extends AbstractConnectionFactory {
private final ReadWriteLock theConnectionLock = new ReentrantReadWriteLock();
private volatile TcpConnectionSupport theConnection;
private volatile boolean manualListenerRegistration;
/**
* Constructs a factory that will established connections to the host and port.
* @param host The host.
* @param port The port.
*/
public AbstractClientConnectionFactory(String host, int port) {
super(host, port);
}
/**
* Set whether to automatically (default) or manually add a {@link TcpListener} to the
* connections created by this factory. By default, the factory automatically configures
* the listener. When manual registration is in place, incoming messages will be delayed
* until the listener is registered.
* @since 1.4.5
*/
public void enableManualListenerRegistration() {
this.manualListenerRegistration = true;
}
/**
* Obtains a connection - if {@link #setSingleUse(boolean)} was called with
* true, a new connection is returned; otherwise a single connection is
* reused for all requests while the connection remains open.
*/
@Override
public TcpConnectionSupport getConnection() throws Exception {
this.checkActive();
return obtainConnection();
}
protected TcpConnectionSupport obtainConnection() throws Exception {
if (!this.isSingleUse()) {
TcpConnectionSupport connection = this.obtainSharedConnection();
if (connection != null) {
return connection;
}
}
return obtainNewConnection();
}
protected final TcpConnectionSupport obtainSharedConnection() throws InterruptedException {
this.theConnectionLock.readLock().lockInterruptibly();
try {
TcpConnectionSupport theConnection = this.getTheConnection();
if (theConnection != null && theConnection.isOpen()) {
return theConnection;
}
}
finally {
this.theConnectionLock.readLock().unlock();
}
return null;
}
protected final TcpConnectionSupport obtainNewConnection() throws Exception {
boolean singleUse = this.isSingleUse();
if (!singleUse) {
this.theConnectionLock.writeLock().lockInterruptibly();
}
try {
TcpConnectionSupport connection;
if (!singleUse) {
// Another write lock holder might have created a new one by now.
connection = this.obtainSharedConnection();
if (connection != null) {
return connection;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Opening new socket connection to " + this.getHost() + ":" + this.getPort());
}
connection = this.buildNewConnection();
if (!singleUse) {
this.setTheConnection(connection);
}
connection.publishConnectionOpenEvent();
return connection;
}
catch (Exception e) {
ApplicationEventPublisher applicationEventPublisher = getApplicationEventPublisher();
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new TcpConnectionFailedEvent(this, e));
}
throw e;
}
finally {
if (!singleUse) {
this.theConnectionLock.writeLock().unlock();
}
}
}
protected TcpConnectionSupport buildNewConnection() throws Exception {
throw new UnsupportedOperationException("Factories that don't override this class' obtainConnection() must implement this method");
}
/**
* Transfers attributes such as (de)serializers, singleUse etc to a new connection.
* When the connection factory has a reference to a TCPListener (to read
* responses), or for single use connections, the connection is executed.
* Single use connections need to read from the connection in order to
* close it after the socket timeout.
* @param connection The new connection.
* @param socket The new socket.
*/
protected void initializeConnection(TcpConnectionSupport connection, Socket socket) {
if (this.manualListenerRegistration) {
connection.enableManualListenerRegistration();
}
else {
TcpListener listener = this.getListener();
if (listener != null) {
connection.registerListener(listener);
}
}
TcpSender sender = this.getSender();
if (sender != null) {
connection.registerSender(sender);
}
connection.setMapper(this.getMapper());
connection.setDeserializer(this.getDeserializer());
connection.setSerializer(this.getSerializer());
}
/**
* @param theConnection the theConnection to set
*/
protected void setTheConnection(TcpConnectionSupport theConnection) {
this.theConnection = theConnection;
}
/**
* @return the theConnection
*/
protected TcpConnectionSupport getTheConnection() {
return this.theConnection;
}
/**
* Force close the connection and null the field if it's
* a shared connection.
*
* @param connection The connection.
*/
public void forceClose(TcpConnection connection) {
if (this.theConnection == connection) {
this.theConnection = null;
}
connection.close();
}
}