/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.invocation.pooled.server;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.security.PrivilegedExceptionAction;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.lang.reflect.Method;
import java.rmi.NoSuchObjectException;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.net.SocketFactory;
import javax.net.ServerSocketFactory;
import org.jboss.bootstrap.spi.util.ServerConfigUtil;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.pooled.interfaces.PooledInvokerProxy;
import org.jboss.invocation.pooled.interfaces.ServerAddress;
import org.jboss.invocation.pooled.interfaces.PooledMarshalledInvocation;
import org.jboss.logging.Logger;
import org.jboss.proxy.TransactionInterceptor;
import org.jboss.system.Registry;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.tm.TransactionPropagationContextFactory;
import org.jboss.tm.TransactionPropagationContextImporter;
import org.jboss.tm.TransactionPropagationContextUtil;
import org.jboss.security.SecurityDomain;
import org.jboss.net.sockets.DefaultSocketFactory;
/**
* This invoker pools Threads and client connections to one server socket.
* The purpose is to avoid a bunch of failings of RMI.
*
* 1. Avoid making a client socket connection with every invocation call.
* This is very expensive. Also on windows if too many clients try
* to connect at the same time, you get connection refused exceptions.
* This invoker/proxy combo alleviates this.
*
* 2. Avoid creating a thread per invocation. The client/server connection
* is preserved and attached to the same thread.
* So we have connection pooling on the server and client side, and thread pooling
* on the server side. Pool, is an LRU pool, so resources should be cleaned up.
*
*
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @author Scott.Stark@jboss.org
* @version $Revision: 81030 $
*
* @jmx:mbean extends="org.jboss.system.ServiceMBean"
*/
public class PooledInvoker extends ServiceMBeanSupport
implements PooledInvokerMBean, Runnable
{
/**
* logger instance.
*/
final static protected Logger log = Logger.getLogger(PooledInvoker.class);
/**
* If the TcpNoDelay option should be used on the socket.
*/
protected boolean enableTcpNoDelay = false;
/**
* The internet address to bind to by default.
*/
protected String serverBindAddress = null;
/**
* The server port to bind to.
*/
protected int serverBindPort = 0;
/**
* The internet address client will use to connect to the sever.
*/
protected String clientConnectAddress = null;
/**
* The port a client will use to connect to the sever.
*/
protected int clientConnectPort = 0;
/**
* The number of retry attempts on
*/
protected int clientRetryCount = 1;
protected int backlog = 200;
/** The class name of the optional custom client socket factory */
protected String clientSocketFactoryName;
/** The class name of the optional custom server socket factory */
protected String serverSocketFactoryName;
/** An optional custom client socket factory */
protected SocketFactory clientSocketFactory;
/** An optional custom server socket factory */
protected ServerSocketFactory serverSocketFactory;
/** The server socket for */
protected ServerSocket serverSocket = null;
/** The name of the security domain to use with server sockets that support SSL */
protected String sslDomain;
protected int timeout = 60000; // 60 seconds.
protected int maxPoolSize = 300;
protected int clientMaxPoolSize = 300;
protected int numAcceptThreads = 1;
protected Thread[] acceptThreads;
protected LRUPool clientpool;
protected LinkedList threadpool;
protected boolean running = true;
/** The logging trace level flag */
protected boolean trace = false;
/**
* ObjectName of the <code>transactionManagerService</code> we use.
* Probably should not be here -- used to set txInterceptor tx mananger.
*/
protected ObjectName transactionManagerService;
protected PooledInvokerProxy optimizedInvokerProxy = null;
/** A priviledged actions for MBeanServer.invoke when running with sec mgr */
private MBeanServerAction serverAction = new MBeanServerAction();
protected static TransactionPropagationContextFactory tpcFactory;
protected static TransactionPropagationContextImporter tpcImporter;
////////////////////////////////////////////////////////////////////////
//
// The following methods Override the ServiceMBeanSupport base class
//
////////////////////////////////////////////////////////////////////////
protected void jmxBind()
{
Registry.bind(getServiceName(), optimizedInvokerProxy);
}
/**
* Starts this IL, and binds it to JNDI
*
* @exception Exception Description of Exception
*/
public void startService() throws Exception
{
trace = log.isTraceEnabled();
///////////////////////////////////////////////////////////
// Setup the transaction stuff
///////////////////////////////////////////////////////////
InitialContext ctx = new InitialContext();
// Get the transaction propagation context factory
tpcFactory = TransactionPropagationContextUtil.getTPCFactory();
// and the transaction propagation context importer
tpcImporter = TransactionPropagationContextUtil.getTPCImporter();
// FIXME marcf: This should not be here
TransactionInterceptor.setTransactionManager((TransactionManager)ctx.lookup("java:/TransactionManager"));
///////////////////////////////////////////////////////////
// Setup the socket level stuff
///////////////////////////////////////////////////////////
InetAddress bindAddress =
(serverBindAddress == null || serverBindAddress.length() == 0)
? null
: InetAddress.getByName(serverBindAddress);
clientConnectAddress =
(clientConnectAddress == null || clientConnectAddress.length() == 0)
? InetAddress.getLocalHost().getHostName()
: clientConnectAddress;
/* We need to check the address against "0.0.0.0" as this is not a valid
address although some jdks will default to the host, while others fail
with java.net.BindException: Cannot assign requested address: connect
*/
clientConnectAddress = ServerConfigUtil.fixRemoteAddress(clientConnectAddress);
// Load any custom socket factories
loadCustomSocketFactories();
clientpool = new LRUPool(2, maxPoolSize);
clientpool.create();
threadpool = new LinkedList();
try
{
if( serverSocketFactory != null )
serverSocket = serverSocketFactory.createServerSocket(serverBindPort, backlog, bindAddress);
else
serverSocket = new ServerSocket(serverBindPort, backlog, bindAddress);
}
catch( java.net.BindException be)
{
throw new Exception("Port "+serverBindPort+" is already in use",be);
}
serverBindPort = serverSocket.getLocalPort();
clientConnectPort = (clientConnectPort == 0) ? serverSocket.getLocalPort() : clientConnectPort;
ServerAddress sa = new ServerAddress(clientConnectAddress, clientConnectPort,
enableTcpNoDelay, timeout, clientSocketFactory);
optimizedInvokerProxy = new PooledInvokerProxy(sa, clientMaxPoolSize, clientRetryCount);
///////////////////////////////////////////////////////////
// Register the service with the rest of the JBoss Kernel
///////////////////////////////////////////////////////////
// Export references to the bean
jmxBind();
log.debug("Bound invoker for JMX node");
ctx.close();
acceptThreads = new Thread[numAcceptThreads];
for (int i = 0; i < numAcceptThreads; i++)
{
String name = "PooledInvokerAcceptor#"+i+"-"+serverBindPort;
acceptThreads[i] = new Thread(this, name);
acceptThreads[i].start();
}
}
public void run()
{
while (running)
{
try
{
Socket socket = serverSocket.accept();
if( trace )
log.trace("Accepted: "+socket);
ServerThread thread = null;
boolean newThread = false;
while (thread == null)
{
synchronized(threadpool)
{
if (threadpool.size() > 0)
{
thread = (ServerThread)threadpool.removeFirst();
}
}
if (thread == null)
{
synchronized(clientpool)
{
if (clientpool.size() < maxPoolSize)
{
thread = new ServerThread(socket, this, clientpool, threadpool, timeout);
newThread = true;
}
if (thread == null)
{
clientpool.evict();
if( trace )
log.trace("Waiting for a thread...");
clientpool.wait();
if( trace )
log.trace("Notified of available thread");
}
}
}
}
synchronized(clientpool)
{
clientpool.insert(thread, thread);
}
if (newThread)
{
if( trace )
log.trace("Created a new thread, t="+thread);
thread.start();
}
else
{
if( trace )
log.trace("Reusing thread t="+thread);
thread.wakeup(socket, timeout);
}
}
catch (Throwable ex)
{
if (running)
log.error("Failed to accept socket connection", ex);
}
}
}
/**
* Stops this service, and unbinds it from JNDI.
*/
public void stopService() throws Exception
{
running = false;
maxPoolSize = 0; // so ServerThreads don't reinsert themselves
for (int i = 0; i < acceptThreads.length; i++)
{
try
{
acceptThreads[i].interrupt();
}
catch (Exception ignored){}
}
clientpool.flush();
for (int i = 0; i < threadpool.size(); i++)
{
ServerThread thread = (ServerThread)threadpool.removeFirst();
thread.shutdown();
}
try
{
serverSocket.close();
}
catch(Exception e)
{
}
}
protected void destroyService() throws Exception
{
// Unexport references to the bean
Registry.unbind(getServiceName());
}
/**
* The ServerProtocol will use this method to service an invocation
* request.
*/
public Object invoke(Invocation invocation) throws Exception
{
Thread currentThread = Thread.currentThread();
ClassLoader oldCl = currentThread.getContextClassLoader();
try
{
// Deserialize the transaction if it is there
PooledMarshalledInvocation mi = (PooledMarshalledInvocation) invocation;
invocation.setTransaction(importTPC(mi.getTransactionPropagationContext()));
ObjectName mbean = (ObjectName) Registry.lookup(invocation.getObjectName());
if( mbean == null )
{
System.err.println("NoSuchObjectException: "+invocation.getObjectName());
throw new NoSuchObjectException("Failed to find target for objectName: "+invocation.getObjectName());
}
// The cl on the thread should be set in another interceptor
Object obj = serverAction.invoke(mbean, "invoke",
new Object[] { invocation }, Invocation.INVOKE_SIGNATURE);
return obj;
}
catch (Exception e)
{
org.jboss.mx.util.JMXExceptionDecoder.rethrow(e);
// the compiler does not know an exception is thrown by the above
throw new org.jboss.util.UnreachableStatementException();
}
finally
{
currentThread.setContextClassLoader(oldCl);
}
}
protected Transaction importTPC(Object tpc)
{
if (tpc != null)
return tpcImporter.importTransactionPropagationContext(tpc);
return null;
}
//The following are the mbean attributes for TrunkInvoker
/**
* Getter for property numAcceptThreads
*
* @return Value of property numAcceptThreads
* @jmx:managed-attribute
*/
public int getNumAcceptThreads()
{
return numAcceptThreads;
}
/**
* Setter for property numAcceptThreads
*
* @param size New value of property numAcceptThreads.
* @jmx:managed-attribute
*/
public void setNumAcceptThreads(int size)
{
this.numAcceptThreads = size;
}
/**
* Getter for property maxPoolSize;
*
* @return Value of property maxPoolSize.
* @jmx:managed-attribute
*/
public int getMaxPoolSize()
{
return maxPoolSize;
}
/**
* Setter for property maxPoolSize.
*
* @param maxPoolSize New value of property maxPoolSize.
* @jmx:managed-attribute
*/
public void setMaxPoolSize(int maxPoolSize)
{
this.maxPoolSize = maxPoolSize;
}
/**
* Getter for property maxPoolSize;
*
* @return Value of property maxPoolSize.
* @jmx:managed-attribute
*/
public int getClientMaxPoolSize()
{
return clientMaxPoolSize;
}
/**
* Setter for property maxPoolSize.
*
* @param clientMaxPoolSize New value of property serverBindPort.
* @jmx:managed-attribute
*/
public void setClientMaxPoolSize(int clientMaxPoolSize)
{
this.clientMaxPoolSize = clientMaxPoolSize;
}
/**
* Getter for property timeout
*
* @return Value of property timeout
* @jmx:managed-attribute
*/
public int getSocketTimeout()
{
return timeout;
}
/**
* Setter for property timeout
*
* @param time New value of property timeout
* @jmx:managed-attribute
*/
public void setSocketTimeout(int time)
{
this.timeout = time;
}
/**
*
* @return Value of property CurrentClientPoolSize.
* @jmx:managed-attribute
*/
public int getCurrentClientPoolSize()
{
return clientpool.size();
}
/**
*
* @return Value of property CurrentThreadPoolSize.
* @jmx:managed-attribute
*/
public int getCurrentThreadPoolSize()
{
return threadpool.size();
}
/**
* Getter for property serverBindPort.
*
* @return Value of property serverBindPort.
* @jmx:managed-attribute
*/
public int getServerBindPort()
{
return serverBindPort;
}
/**
* Setter for property serverBindPort.
*
* @param serverBindPort New value of property serverBindPort.
* @jmx:managed-attribute
*/
public void setServerBindPort(int serverBindPort)
{
this.serverBindPort = serverBindPort;
}
/**
* @jmx:managed-attribute
*/
public String getClientConnectAddress()
{
return clientConnectAddress;
}
/**
* @jmx:managed-attribute
*/
public void setClientConnectAddress(String clientConnectAddress)
{
this.clientConnectAddress = clientConnectAddress;
}
/**
* @jmx:managed-attribute
*/
public int getClientConnectPort()
{
return clientConnectPort;
}
/**
* @jmx:managed-attribute
*/
public void setClientConnectPort(int clientConnectPort)
{
this.clientConnectPort = clientConnectPort;
}
/**
* @jmx:managed-attribute
*/
public int getClientRetryCount()
{
return clientRetryCount;
}
/**
* @jmx:managed-attribute
*/
public void setClientRetryCount(int clientRetryCount)
{
this.clientRetryCount = clientRetryCount;
}
/**
* @jmx:managed-attribute
*/
public int getBacklog()
{
return backlog;
}
/**
* @jmx:managed-attribute
*/
public void setBacklog(int backlog)
{
this.backlog = backlog;
}
/**
* @jmx:managed-attribute
*/
public boolean isEnableTcpNoDelay()
{
return enableTcpNoDelay;
}
/**
* @jmx:managed-attribute
*/
public void setEnableTcpNoDelay(boolean enableTcpNoDelay)
{
this.enableTcpNoDelay = enableTcpNoDelay;
}
/**
* @jmx:managed-attribute
*/
public String getServerBindAddress()
{
return serverBindAddress;
}
/**
* @jmx:managed-attribute
*/
public void setServerBindAddress(String serverBindAddress)
{
this.serverBindAddress = serverBindAddress;
}
/**
* @jmx:managed-attribute
*/
public String getClientSocketFactoryName()
{
return clientSocketFactoryName;
}
/**
* @jmx:managed-attribute
*/
public void setClientSocketFactoryName(String clientSocketFactoryName)
{
this.clientSocketFactoryName = clientSocketFactoryName;
}
/**
* @jmx:managed-attribute
*/
public String getServerSocketFactoryName()
{
return serverSocketFactoryName;
}
/**
* @jmx:managed-attribute
*/
public void setServerSocketFactoryName(String serverSocketFactoryName)
{
this.serverSocketFactoryName = serverSocketFactoryName;
}
/**
* @jmx:managed-attribute
*/
public SocketFactory getClientSocketFactory()
{
return clientSocketFactory;
}
/**
* @jmx:managed-attribute
*/
public void setClientSocketFactory(SocketFactory clientSocketFactory)
{
this.clientSocketFactory = clientSocketFactory;
}
/**
* @jmx:managed-attribute
*/
public ServerSocket getServerSocket()
{
return serverSocket;
}
/**
* @jmx:managed-attribute
*/
public void setServerSocket(ServerSocket serverSocket)
{
this.serverSocket = serverSocket;
}
/**
* @jmx:managed-attribute
*/
public String getSslDomain()
{
return sslDomain;
}
/**
* @jmx:managed-attribute
*/
public void setSslDomain(String sslDomain)
{
this.sslDomain = sslDomain;
}
/**
* @jmx:managed-attribute
*/
public ServerSocketFactory getServerSocketFactory()
{
return serverSocketFactory;
}
/**
* @jmx:managed-attribute
*/
public void setServerSocketFactory(ServerSocketFactory serverSocketFactory)
{
this.serverSocketFactory = serverSocketFactory;
}
/**
* mbean get-set pair for field transactionManagerService
* Get the value of transactionManagerService
* @return value of transactionManagerService
*
* @jmx:managed-attribute
*/
public ObjectName getTransactionManagerService()
{
return transactionManagerService;
}
/**
* Set the value of transactionManagerService
* @param transactionManagerService Value to assign to transactionManagerService
*
* @jmx:managed-attribute
*/
public void setTransactionManagerService(ObjectName transactionManagerService)
{
this.transactionManagerService = transactionManagerService;
}
/**
* @jmx:managed-attribute
*/
public PooledInvokerProxy getOptimizedInvokerProxy()
{
return optimizedInvokerProxy;
}
/** Load and instantiate the clientSocketFactory, serverSocketFactory using
the TCL and set the bind address and SSL domain if the serverSocketFactory
supports it.
*/
protected void loadCustomSocketFactories()
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try
{
if( clientSocketFactoryName != null )
{
Class csfClass = loader.loadClass(clientSocketFactoryName);
clientSocketFactory = (SocketFactory) csfClass.newInstance();
}
}
catch (Exception e)
{
log.error("Failed to load client socket factory", e);
clientSocketFactory = null;
}
try
{
if( serverSocketFactory == null )
{
if( serverSocketFactoryName != null )
{
Class ssfClass = loader.loadClass(serverSocketFactoryName);
serverSocketFactory = (ServerSocketFactory) ssfClass.newInstance();
if( serverBindAddress != null )
{
// See if the server socket supports setBindAddress(String)
try
{
Class[] parameterTypes = {String.class};
Method m = ssfClass.getMethod("setBindAddress", parameterTypes);
Object[] args = {serverBindAddress};
m.invoke(serverSocketFactory, args);
}
catch (NoSuchMethodException e)
{
log.warn("Socket factory does not support setBindAddress(String)");
// Go with default address
}
catch (Exception e)
{
log.warn("Failed to setBindAddress="+serverBindAddress+" on socket factory", e);
// Go with default address
}
}
/* See if the server socket supports setSecurityDomain(SecurityDomain)
if an sslDomain was specified
*/
if( sslDomain != null )
{
try
{
InitialContext ctx = new InitialContext();
SecurityDomain domain = (SecurityDomain) ctx.lookup(sslDomain);
Class[] parameterTypes = {SecurityDomain.class};
Method m = ssfClass.getMethod("setSecurityDomain", parameterTypes);
Object[] args = {domain};
m.invoke(serverSocketFactory, args);
}
catch(NoSuchMethodException e)
{
log.error("Socket factory does not support setSecurityDomain(SecurityDomain)");
}
catch(Exception e)
{
log.error("Failed to setSecurityDomain="+sslDomain+" on socket factory", e);
}
}
}
// If a bind address was specified create a DefaultSocketFactory
else if( serverBindAddress != null )
{
DefaultSocketFactory defaultFactory = new DefaultSocketFactory(backlog);
serverSocketFactory = defaultFactory;
try
{
defaultFactory.setBindAddress(serverBindAddress);
}
catch (UnknownHostException e)
{
log.error("Failed to setBindAddress="+serverBindAddress+" on socket factory", e);
}
}
}
}
catch (Exception e)
{
log.error("operation failed", e);
serverSocketFactory = null;
}
}
/** Perform the MBeanServer.invoke op in a PrivilegedExceptionAction if
* running with a security manager.
*/
class MBeanServerAction implements PrivilegedExceptionAction
{
private ObjectName target;
String method;
Object[] args;
String[] sig;
MBeanServerAction()
{
}
MBeanServerAction(ObjectName target, String method, Object[] args, String[] sig)
{
this.target = target;
this.method = method;
this.args = args;
this.sig = sig;
}
public Object run() throws Exception
{
Object rtnValue = server.invoke(target, method, args, sig);
return rtnValue;
}
Object invoke(ObjectName target, String method, Object[] args, String[] sig)
throws Exception
{
SecurityManager sm = System.getSecurityManager();
Object rtnValue = null;
if( sm == null )
{
// Direct invocation on MBeanServer
rtnValue = server.invoke(target, method, args, sig);
}
else
{
try
{
// Encapsulate the invocation in a PrivilegedExceptionAction
MBeanServerAction action = new MBeanServerAction(target, method, args, sig);
rtnValue = AccessController.doPrivileged(action);
}
catch (PrivilegedActionException e)
{
Exception ex = e.getException();
throw ex;
}
}
return rtnValue;
}
}
}
// vim:expandtab:tabstop=3:shiftwidth=3