/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.remoting.transport.socket;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.rmi.MarshalException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import org.jboss.remoting.CannotConnectException;
import org.jboss.remoting.ConnectionFailedException;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.RemoteClientInvoker;
import org.jboss.remoting.marshal.Marshaller;
import org.jboss.remoting.marshal.UnMarshaller;
import org.jboss.remoting.marshal.serializable.SerializableMarshaller;
import javax.net.SocketFactory;
/**
* SocketClientInvoker uses Sockets to remotely connect to the a remote ServerInvoker, which
* must be a SocketServerInvoker.
*
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
* @version $Revision: 1.30.2.1 $
*/
public class SocketClientInvoker extends RemoteClientInvoker
{
private InetAddress addr;
private int port;
public static final String TCP_NODELAY_FLAG = "enableTcpNoDelay";
public static final String MAX_POOL_SIZE_FLAG = "clientMaxPoolSize";
public static final String SO_TIMEOUT_FLAG = "timeout";
public static final String CLIENT_SOCKET_CLASS_FLAG = "clientSocketClass";
/**
* Default value for socket timeout is 30 minutes.
*/
public static final int SO_TIMEOUT_DEFAULT = 1800000;
public static final boolean TCP_NODELAY_DEFAULT = false;
private boolean shouldCheckConnection = true;
// Performance measurements
public static long getSocketTime = 0;
public static long readTime = 0;
public static long writeTime = 0;
public static long serializeTime = 0;
public static long deserializeTime = 0;
/**
* If the TcpNoDelay option should be used on the socket.
*/
protected boolean enableTcpNoDelay = TCP_NODELAY_DEFAULT;
protected int timeout = SO_TIMEOUT_DEFAULT;
protected String clientSocketClassName = ClientSocketWrapper.class.getName();
private Constructor clientSocketConstructor = null;
/**
* Set number of retries in getSocket method
*/
public static final int MAX_RETRIES = 3;
public static long usedPooled = 0;
protected int numberOfRetries = MAX_RETRIES;
/**
* Pool for this invoker. This is shared between all
* instances of proxies attached to a specific invoker
*/
protected LinkedList pool = null;
/**
* connection information
*/
protected ServerAddress address;
protected HashMap connectionPools = new HashMap();
protected int maxPoolSize = 10;
public SocketClientInvoker(InvokerLocator locator)
throws IOException
{
this(locator, null);
}
public SocketClientInvoker(InvokerLocator locator, Map configuration)
throws IOException
{
super(locator, configuration);
try
{
setup();
}
catch(Exception ex)
{
throw new RuntimeException(ex.getMessage());
}
}
protected void setup()
throws Exception
{
this.addr = InetAddress.getByName(locator.getHost());
this.port = locator.getPort();
configureParameters();
address = new ServerAddress(addr.getHostAddress(), port, enableTcpNoDelay, timeout);
}
protected void configureParameters()
{
Map params = configuration;
if(params != null)
{
// look for enableTcpNoDelay param
Object val = params.get(TCP_NODELAY_FLAG);
if(val != null)
{
try
{
boolean bVal = Boolean.valueOf((String) val).booleanValue();
enableTcpNoDelay = bVal;
log.debug("Setting SocketClientInvoker::enableTcpNoDelay to: " + enableTcpNoDelay);
}
catch(Exception e)
{
log.warn("Could not convert " + TCP_NODELAY_FLAG + " value of " + val + " to a boolean value.");
}
}
// look for maxPoolSize param
val = params.get(MAX_POOL_SIZE_FLAG);
if(val != null)
{
try
{
int nVal = Integer.valueOf((String) val).intValue();
maxPoolSize = nVal;
log.debug("Setting SocketClientInvoker::maxPoolSize to: " + maxPoolSize);
}
catch(Exception e)
{
log.warn("Could not convert " + MAX_POOL_SIZE_FLAG + " value of " + val + " to a int value.");
}
}
// look for socketTimeout param
val = params.get(SO_TIMEOUT_FLAG);
if(val != null)
{
try
{
int nVal = Integer.valueOf((String) val).intValue();
timeout = nVal;
log.debug("Setting SocketClientInvoker::timeout to: " + timeout);
}
catch(Exception e)
{
log.warn("Could not convert " + SO_TIMEOUT_FLAG + " value of " + val + " to a int value.");
}
}
// look for client socket class name
val = params.get(CLIENT_SOCKET_CLASS_FLAG);
if(val != null)
{
String value = (String) val;
if(value.length() > 0)
{
clientSocketClassName = value;
log.debug("Setting ClientSocket class name to: " + clientSocketClassName);
}
}
val = params.get(SocketServerInvoker.CHECK_CONNECTION_KEY);
if(val != null)
{
String value = (String)val;
if(value.length() > 0)
{
shouldCheckConnection = Boolean.valueOf(value).booleanValue();
}
}
}
}
protected void finalize() throws Throwable
{
disconnect();
super.finalize();
}
protected synchronized void handleConnect()
throws ConnectionFailedException
{
initPool();
}
protected synchronized void handleDisconnect()
{
clearPools();
}
/**
* Each implementation of the remote client invoker should have
* a default data type that is uses in the case it is not specified
* in the invoker locator uri.
*
* @return
*/
protected String getDefaultDataType()
{
return SerializableMarshaller.DATATYPE;
}
/**
* @param sessionId
* @param invocation
* @param marshaller
* @return
* @throws java.io.IOException
* @throws org.jboss.remoting.ConnectionFailedException
*
*/
protected Object transport(String sessionId, Object invocation, Map metadata,
Marshaller marshaller, UnMarshaller unmarshaller)
throws IOException, ConnectionFailedException, ClassNotFoundException
{
Object response = null;
long start = System.currentTimeMillis();
SocketWrapper socketWrapper = null;
try
{
socketWrapper = getConnection();
}
catch(Exception e)
{
throw new CannotConnectException("Can not get connection to server. Problem establishing socket connection.", e);
}
long end = System.currentTimeMillis() - start;
getSocketTime += end;
try
{
marshaller.write(invocation, socketWrapper.getOutputStream());
end = System.currentTimeMillis() - start;
writeTime += end;
start = System.currentTimeMillis();
response = unmarshaller.read(socketWrapper.getInputStream(), null);
end = System.currentTimeMillis() - start;
readTime += end;
}
catch(Exception ex)
{
try
{
socketWrapper.close();
}
catch(Exception ignored)
{
}
log.error("Got marshalling exception, exiting", ex);
if(ex instanceof ClassNotFoundException)
{
//TODO: -TME Add better exception handling for class not found exception
log.error("Error loading classes from remote call result.", ex);
throw (ClassNotFoundException) ex;
}
else if(ex instanceof SocketTimeoutException)
{
throw new MarshalException("Socket timed out. Waited " + socketWrapper.getTimeout() + " milliseconds for response while calling on " +
getLocator(), ex);
}
throw new MarshalException("Failed to communicate. Problem during marshalling/unmarshalling", ex);
}
// Put socket back in pool for reuse
synchronized(pool)
{
if(pool.size() < maxPoolSize)
{
pool.add(socketWrapper);
}
else
{
if(log.isTraceEnabled())
{
log.trace("Pool was already full, than we will close the connection");
}
try
{
socketWrapper.close();
}
catch(Exception ignored)
{
}
}
}
// Return response
if(log.isTraceEnabled())
{
log.trace("Response: " + response);
}
return response;
}
/**
* Close all sockets in a specific pool.
*/
public void clearPool(ServerAddress sa)
{
try
{
LinkedList thepool = (LinkedList) connectionPools.get(sa);
if(thepool == null)
{
return;
}
synchronized(thepool)
{
int size = thepool.size();
for(int i = 0; i < size; i++)
{
SocketWrapper socketWrapper = (SocketWrapper) thepool.removeFirst();
try
{
socketWrapper.close();
socketWrapper = null;
}
catch(Exception ignored)
{
}
}
}
}
catch(Exception ex)
{
// ignored
}
}
/**
* Close all sockets in all pools
*/
public void clearPools()
{
synchronized(connectionPools)
{
Iterator it = connectionPools.keySet().iterator();
while(it.hasNext())
{
ServerAddress sa = (ServerAddress) it.next();
clearPool(sa);
}
}
}
protected void initPool()
{
synchronized(connectionPools)
{
pool = (LinkedList) connectionPools.get(address);
if(pool == null)
{
pool = new LinkedList();
connectionPools.put(address, pool);
}
}
}
/**
* Sets the number of retries to get a socket connection.
*
* @param numberOfRetries Must be a number greater than 0
*/
public void setNumberOfRetries(int numberOfRetries)
{
if(numberOfRetries < 1)
{
this.numberOfRetries = MAX_RETRIES;
}
else
{
this.numberOfRetries = numberOfRetries;
}
}
public int getNumberOfRetries()
{
return numberOfRetries;
}
/**
* used for debugging (tracing) connections leaks
*/
static int counter = 0;
protected SocketWrapper getConnection() throws Exception
{
Exception failed = null;
Socket socket = null;
//
// Need to retry a few times
// on socket connection because, at least on Windoze,
// if too many concurrent threads try to connect
// at same time, you get ConnectionRefused
//
// Retrying seems to be the most performant.
//
// This problem always happens with RMI and seems to
// have nothing to do with backlog or number of threads
// waiting in accept() on the server.
//
for(int i = 0; i < numberOfRetries; i++)
{
synchronized(pool)
{
if(pool.size() > 0)
{
SocketWrapper pooled = getPooledConnection();
if(pooled != null)
{
usedPooled++;
return pooled;
}
}
}
try
{
if(log.isTraceEnabled())
{
log.trace("Creating socket number " + (counter++));
}
socket = createSocket(address.address, address.port);
break;
}
catch(Exception ex)
{
if(i + 1 < MAX_RETRIES)
{
Thread.sleep(1);
continue;
}
throw ex;
}
}
socket.setTcpNoDelay(address.enableTcpNoDelay);
return createClientSocket(socket, address.timeout, getLocator().getParameters());
}
protected SocketWrapper createClientSocket(Socket socket, int timeout, Map metadata) throws Exception
{
if(clientSocketConstructor == null)
{
ClassLoader classLoader = getClassLoader();
if(classLoader == null)
{
classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
{
classLoader = getClass().getClassLoader();
}
}
Class cl = classLoader.loadClass(clientSocketClassName);
try
{
clientSocketConstructor = cl.getConstructor(new Class[]{Socket.class, Map.class, Integer.class});
}
catch(NoSuchMethodException e)
{
clientSocketConstructor = cl.getConstructor(new Class[]{Socket.class});
}
}
SocketWrapper clientSocketWrapper = null;
if(clientSocketConstructor.getParameterTypes().length == 3)
{
clientSocketWrapper = (SocketWrapper) clientSocketConstructor.newInstance(new Object[]{socket, metadata, new Integer(timeout)});
}
else
{
clientSocketWrapper = (SocketWrapper) clientSocketConstructor.newInstance(new Object[]{socket});
clientSocketWrapper.setTimeout(timeout);
}
return clientSocketWrapper;
}
protected Socket createSocket(String address, int port) throws IOException
{
SocketFactory socketFactory = getSocketFactory();
if (socketFactory != null)
return socketFactory.createSocket(address, port);
else
return new Socket(address, port);
}
protected SocketWrapper getPooledConnection()
{
SocketWrapper socketWrapper = null;
while(pool.size() > 0)
{
socketWrapper = (SocketWrapper) pool.removeFirst();
try
{
if(socketWrapper != null)
{
if(shouldCheckConnection)
{
socketWrapper.checkConnection();
return socketWrapper;
}
else
{
if(socketWrapper.getSocket().isConnected())
{
return socketWrapper;
}
else
{
try
{
socketWrapper.close();
}
catch(IOException e)
{
}
return null;
}
}
}
}
catch(Exception ex)
{
if(log.isTraceEnabled())
{
log.trace("Couldn't reuse connection from pool");
}
try
{
socketWrapper.close();
}
catch(Exception ignored)
{
}
}
}
return null;
}
/**
* The name of of the server.
*/
public String getServerHostName() throws Exception
{
return address.address;
}
}