/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2011-2012 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.provision.support;
import java.net.SocketAddress;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IoSessionInitializer;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.opennms.core.utils.LogUtils;
/**
* <p>
* Factory for encapsulating a {@link NioSocketConnector} in such a way as to allow us
* to reuse the connector for each {@link #connect(SocketAddress, SocketAddress, IoSessionInitializer, IoHandler)}
* call.
* </p>
*
* <p>
* There will be one ConnectionFactory for each discrete connection timeout
* value.
* </p>
*
* @author Seth
* @author ranger
* @author Duncan Mackintosh
*/
public class ConnectionFactoryConnectorPoolImpl extends ConnectionFactory {
/**
* The connector that will be reused for each incoming connection.
*/
private NioSocketConnector m_connector;
/**
* A mutex that protects the connector instance since we must dispose() and
* recreate it if it encounters errors.
*/
private final Object m_connectorMutex = new Object();
/**
* Create a new factory. Private because one should use {@link #getFactory(int)}
*/
protected ConnectionFactoryConnectorPoolImpl(int timeoutInMillis) {
super(timeoutInMillis);
}
private static final NioSocketConnector getSocketConnector(long timeout, IoHandler handler) {
NioSocketConnector connector = new NioSocketConnector();
connector.setHandler(handler);
connector.setConnectTimeoutMillis(timeout);
return connector;
}
/**
* <p>Connect to a remote socket. If org.opennms.netmgt.provision.maxConcurrentConnections
* is set, this may block until a connection slot is available.</p>
*
* <p>You must dispose both the {@link ConnectionFactoryConnectorPoolImpl} and {@link ConnectFuture} when done
* by calling {@link #dispose(ConnectionFactoryConnectorPoolImpl, ConnectFuture)}.</p>
*
* @param remoteAddress
* Destination address
* @param init
* Initialiser for the IoSession
* @return
* ConnectFuture from a Mina connect call
*/
@Override
public ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, IoSessionInitializer<? extends ConnectFuture> init, IoHandler handler) {
for (int retries = 0; retries < 3; retries++) {
synchronized (m_connectorMutex) {
if (m_connector == null) {
// Sanity check for null connector instance
LogUtils.debugf(this, "Found a null NioSocketConnector, creating a new one with timeout %d", getTimeout());
m_connector = getSocketConnector(getTimeout(), handler);
}
try {
/*
* Set the handler each time since we are reusing this connector for every incoming
* connect() call.
*/
m_connector.setHandler(handler);
/*
* Use the 3-argument call to connect(). If you use the 2-argument version without
* the localhost port, the call will end up doing a name lookup which seems to fail
* intermittently in unit tests.
*
* @see http://issues.opennms.org/browse/NMS-5309
*/
return m_connector.connect(remoteAddress, localAddress, init);
} catch (Throwable e) {
LogUtils.debugf(this, e, "Caught exception on factory %s, retrying: %s", this, e);
m_connector.dispose();
m_connector = getSocketConnector(getTimeout(), handler);
continue;
}
}
}
throw new IllegalStateException("Could not connect to socket because of excessive RejectedExecutionExceptions");
}
/**
* Delegates completely to {@link #connect(SocketAddress, SocketAddress, IoSessionInitializer, IoHandler)}
* since we are reusing the same connector for all invocations.
*
* @param remoteAddress
* @param localAddress
* @param init
* @param handler
*/
@Override
public ConnectFuture reConnect(SocketAddress remoteAddress, SocketAddress localAddress, IoSessionInitializer<? extends ConnectFuture> init, IoHandler handler) {
return connect(remoteAddress, localAddress, init, handler);
}
@Override
protected void dispose() {
synchronized (m_connectorMutex) {
m_connector.dispose();
}
}
}