/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, 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.jrmp.server;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.Serializable;
import java.rmi.server.RemoteServer;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RemoteStub;
import java.rmi.MarshalledObject;
import java.security.PrivilegedAction;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import javax.management.ObjectName;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.naming.Name;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.NameNotFoundException;
import javax.transaction.Transaction;
import org.jboss.beans.metadata.api.annotations.Create;
import org.jboss.beans.metadata.api.annotations.Destroy;
import org.jboss.beans.metadata.api.annotations.Start;
import org.jboss.beans.metadata.api.annotations.Stop;
import org.jboss.invocation.jrmp.interfaces.JRMPInvokerProxy;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.Invoker;
import org.jboss.invocation.MarshalledInvocation;
import org.jboss.invocation.MarshalledValueInputStream;
import org.jboss.kernel.spi.dependency.KernelControllerContext;
import org.jboss.kernel.spi.dependency.KernelControllerContextAware;
import org.jboss.logging.Logger;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.net.sockets.DefaultSocketFactory;
import org.jboss.security.SecurityDomain;
import org.jboss.system.Registry;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.tm.TransactionPropagationContextUtil;
/**
* The JRMPInvoker is an RMI implementation that can generate Invocations
* from RMI/JRMP into the JMX base.
*
* @author <a href="mailto:marc.fleury@jboss.org>Marc Fleury</a>
* @author <a href="mailto:scott.stark@jboss.org>Scott Stark</a>
* @version $Revision: 79760 $
* @jmx.mbean extends="org.jboss.system.ServiceMBean"
*/
public class JRMPInvoker
extends RemoteServer
implements Invoker, JRMPInvokerMBean, MBeanRegistration, KernelControllerContextAware
{
/** @since 4.2.0 */
static final long serialVersionUID = 3110972460891691492L;
/**
* Identifer to instruct the usage of an anonymous port.
*/
public static final int ANONYMOUS_PORT = 0;
/**
* Instance logger.
*/
protected Logger log;
/**
* Service MBean support delegate.
*/
protected ServiceMBeanSupport support;
/**
* The port the container will be exported on
*/
protected int rmiPort = ANONYMOUS_PORT;
/**
* An optional custom client socket factory
*/
protected RMIClientSocketFactory clientSocketFactory;
/**
* An optional custom server socket factory
*/
protected RMIServerSocketFactory serverSocketFactory;
/**
* 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;
/**
* The address to bind the rmi port on
*/
protected String serverAddress;
/**
* The name of the security domain to use with server sockets that support SSL
*/
protected String sslDomain;
protected RemoteStub invokerStub;
/**
* The socket accept backlog
*/
protected int backlog = 200;
/**
* A flag to enable caching of classes in the MarshalledValueInputStream
*/
protected boolean enableClassCaching = false;
/**
* A priviledged actions for MBeanServer.invoke when running with sec mgr
*/
private MBeanServerAction serverAction = new MBeanServerAction();
public JRMPInvoker()
{
final JRMPInvoker delegate = this;
// adapt the support delegate to invoke our state methods
support = new ServiceMBeanSupport(getClass())
{
protected void startService() throws Exception
{
delegate.startService();
}
protected void stopService() throws Exception
{
delegate.stopService();
}
protected void destroyService() throws Exception
{
delegate.destroyService();
}
};
// Setup logging from delegate
log = support.getLog();
}
/**
* @jmx.managed-attribute
*/
public int getBacklog()
{
return backlog;
}
/**
* @jmx.managed-attribute
*/
public void setBacklog(int back)
{
backlog = back;
}
/**
* @jmx.managed-attribute
*/
public boolean getEnableClassCaching()
{
return enableClassCaching;
}
/**
* @jmx.managed-attribute
*/
public void setEnableClassCaching(boolean flag)
{
enableClassCaching = flag;
MarshalledValueInputStream.useClassCache(enableClassCaching);
}
/**
* @return The localhost name or null.
*/
public String getServerHostName()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (Exception ignored)
{
return null;
}
}
/**
* @jmx.managed-attribute
*/
public void setRMIObjectPort(final int rmiPort)
{
this.rmiPort = rmiPort;
}
/**
* @jmx.managed-attribute
*/
public int getRMIObjectPort()
{
return rmiPort;
}
/**
* @jmx.managed-attribute
*/
public void setRMIClientSocketFactory(final String name)
{
clientSocketFactoryName = name;
}
/**
* @jmx.managed-attribute
*/
public String getRMIClientSocketFactory()
{
return clientSocketFactoryName;
}
/**
* @jmx.managed-attribute
*/
public void setRMIClientSocketFactoryBean(final RMIClientSocketFactory bean)
{
clientSocketFactory = bean;
}
/**
* @jmx.managed-attribute
*/
public RMIClientSocketFactory getRMIClientSocketFactoryBean()
{
return clientSocketFactory;
}
/**
* @jmx.managed-attribute
*/
public void setRMIServerSocketFactory(final String name)
{
serverSocketFactoryName = name;
}
/**
* @jmx.managed-attribute
*/
public String getRMIServerSocketFactory()
{
return serverSocketFactoryName;
}
/**
* @jmx.managed-attribute
*/
public void setRMIServerSocketFactoryBean(final RMIServerSocketFactory bean)
{
serverSocketFactory = bean;
}
/**
* @jmx.managed-attribute
*/
public RMIServerSocketFactory getRMIServerSocketFactoryBean()
{
return serverSocketFactory;
}
/**
* @jmx.managed-attribute
*/
public void setServerAddress(final String address)
{
serverAddress = address;
}
/**
* @jmx.managed-attribute
*/
public String getServerAddress()
{
return serverAddress;
}
/**
* @jmx.managed-attribute
*/
public void setSecurityDomain(String domainName)
{
this.sslDomain = domainName;
}
/**
* @jmx.managed-attribute
*/
public String getSecurityDomain()
{
return sslDomain;
}
public Serializable getStub()
{
return this.invokerStub;
}
protected void startService() throws Exception
{
loadCustomSocketFactories();
log.debug("RMI Port='" +
(rmiPort == ANONYMOUS_PORT ? "Anonymous" :
Integer.toString(rmiPort)) + "'");
log.debug("Client SocketFactory='" +
(clientSocketFactory == null ? "Default" :
clientSocketFactory.toString()) + "'");
log.debug("Server SocketFactory='" +
(serverSocketFactory == null ? "Default" :
serverSocketFactory.toString()) + "'");
log.debug("Server SocketAddr='" +
(serverAddress == null ? "Default" :
serverAddress) + "'");
log.debug("SecurityDomain='" +
(sslDomain == null ? "Default" :
sslDomain) + "'");
InitialContext ctx = new InitialContext();
// Validate that there is a TransactionPropagationContextImporter
// bound in JNDI
TransactionPropagationContextUtil.getTPCImporter();
// Set the transaction manager and transaction propagation
// context factory of the GenericProxy class
Invoker delegateInvoker = createDelegateInvoker();
// Make the remote invoker proxy available for use by the proxy factory
Registry.bind(support.getServiceName(), delegateInvoker);
// Export CI
exportCI();
log.debug("Bound JRMP invoker for JMX node");
ctx.close();
}
protected void stopService() throws Exception
{
InitialContext ctx = new InitialContext();
try
{
unexportCI();
}
finally
{
ctx.close();
}
this.clientSocketFactory = null;
this.serverSocketFactory = null;
this.invokerStub = null;
}
protected void destroyService() throws Exception
{
// Export references to the bean
Registry.unbind(support.getServiceName());
}
/**
* Invoke a Remote interface method.
*/
public Object invoke(Invocation invocation)
throws Exception
{
ClassLoader oldCl = TCLAction.UTIL.getContextClassLoader();
ObjectName mbean = null;
try
{
// Deserialize the transaction if it is there
MarshalledInvocation mi = (MarshalledInvocation) invocation;
invocation.setTransaction(importTPC(mi.getTransactionPropagationContext()));
mbean = (ObjectName) Registry.lookup(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 new MarshalledObject(obj);
}
catch (Exception e)
{
Throwable th = JMXExceptionDecoder.decode(e);
if (log.isTraceEnabled())
log.trace("Failed to invoke on mbean: " + mbean, th);
if (th instanceof Exception)
e = (Exception) th;
throw e;
}
finally
{
TCLAction.UTIL.setContextClassLoader(oldCl);
Thread.interrupted(); // clear interruption because this thread may be pooled.
}
}
protected Invoker createDelegateInvoker()
{
return new JRMPInvokerProxy(this);
}
protected void exportCI() throws Exception
{
this.invokerStub = (RemoteStub) UnicastRemoteObject.exportObject
(this, rmiPort, clientSocketFactory, serverSocketFactory);
}
protected void unexportCI() throws Exception
{
UnicastRemoteObject.unexportObject(this, true);
}
protected void rebind(Context ctx, String name, Object val)
throws NamingException
{
// Bind val to name in ctx, and make sure that all
// intermediate contexts exist
Name n = ctx.getNameParser("").parse(name);
while (n.size() > 1)
{
String ctxName = n.get(0);
try
{
ctx = (Context) ctx.lookup(ctxName);
}
catch (NameNotFoundException e)
{
ctx = ctx.createSubcontext(ctxName);
}
n = n.getSuffix(1);
}
ctx.rebind(n.get(0), val);
}
/**
* 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 = TCLAction.UTIL.getContextClassLoader();
if( clientSocketFactory == null )
{
try
{
if (clientSocketFactoryName != null)
{
Class csfClass = loader.loadClass(clientSocketFactoryName);
clientSocketFactory = (RMIClientSocketFactory) csfClass.newInstance();
}
}
catch (Exception e)
{
log.error("Failed to load client socket factory", e);
clientSocketFactory = null;
}
}
if( serverSocketFactory == null )
{
try
{
if (serverSocketFactoryName != null)
{
Class ssfClass = loader.loadClass(serverSocketFactoryName);
serverSocketFactory = (RMIServerSocketFactory) ssfClass.newInstance();
if (serverAddress != null)
{
// See if the server socket supports setBindAddress(String)
try
{
Class[] parameterTypes = {String.class};
Method m = ssfClass.getMethod("setBindAddress", parameterTypes);
Object[] args = {serverAddress};
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=" + serverAddress + " 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 (serverAddress != null)
{
DefaultSocketFactory defaultFactory = new DefaultSocketFactory(backlog);
serverSocketFactory = defaultFactory;
try
{
defaultFactory.setBindAddress(serverAddress);
}
catch (UnknownHostException e)
{
log.error("Failed to setBindAddress=" + serverAddress + " on socket factory", e);
}
}
}
catch (Exception e)
{
log.error("operation failed", e);
serverSocketFactory = null;
}
}
}
/**
* Import a transaction propagation context into the local VM, and
* return the corresponding <code>Transaction</code>.
*
* @return A transaction or null if no tpc.
*/
protected Transaction importTPC(Object tpc)
{
if (tpc != null)
return TransactionPropagationContextUtil.importTPC(tpc);
return null;
}
//
// Delegate the ServiceMBean details to our support delegate
//
public String getName()
{
return support.getName();
}
public MBeanServer getServer()
{
return support.getServer();
}
public int getState()
{
return support.getState();
}
public String getStateString()
{
return support.getStateString();
}
public void create() throws Exception
{
support.create();
}
public void start() throws Exception
{
support.start();
}
public void stop()
{
support.stop();
}
public void destroy()
{
support.destroy();
}
public void jbossInternalLifecycle(String method) throws Exception
{
support.jbossInternalLifecycle(method);
}
public ObjectName preRegister(MBeanServer server, ObjectName name)
throws Exception
{
return support.preRegister(server, name);
}
public void postRegister(Boolean registrationDone)
{
support.postRegister(registrationDone);
}
public void preDeregister() throws Exception
{
support.preDeregister();
}
public void postDeregister()
{
support.postDeregister();
}
public void setKernelControllerContext(KernelControllerContext context) throws Exception
{
support.setKernelControllerContext(context);
}
public void unsetKernelControllerContext(KernelControllerContext context) throws Exception
{
support.unsetKernelControllerContext(context);
}
@Create
public void pojoCreate() throws Exception
{
support.pojoCreate();
}
@Start
public void pojoStart() throws Exception
{
support.pojoStart();
}
@Stop
public void pojoStop() throws Exception
{
support.pojoStop();
}
@Destroy
public void pojoDestroy() throws Exception
{
support.pojoDestroy();
}
interface TCLAction
{
class UTIL
{
static TCLAction getTCLAction()
{
return System.getSecurityManager() == null ? NON_PRIVILEGED : PRIVILEGED;
}
static ClassLoader getContextClassLoader()
{
return getTCLAction().getContextClassLoader();
}
static ClassLoader getContextClassLoader(Thread thread)
{
return getTCLAction().getContextClassLoader(thread);
}
static void setContextClassLoader(ClassLoader cl)
{
getTCLAction().setContextClassLoader(cl);
}
static void setContextClassLoader(Thread thread, ClassLoader cl)
{
getTCLAction().setContextClassLoader(thread, cl);
}
}
TCLAction NON_PRIVILEGED = new TCLAction()
{
public ClassLoader getContextClassLoader()
{
return Thread.currentThread().getContextClassLoader();
}
public ClassLoader getContextClassLoader(Thread thread)
{
return thread.getContextClassLoader();
}
public void setContextClassLoader(ClassLoader cl)
{
Thread.currentThread().setContextClassLoader(cl);
}
public void setContextClassLoader(Thread thread, ClassLoader cl)
{
thread.setContextClassLoader(cl);
}
};
TCLAction PRIVILEGED = new TCLAction()
{
private final PrivilegedAction getTCLPrivilegedAction = new PrivilegedAction()
{
public Object run()
{
return Thread.currentThread().getContextClassLoader();
}
};
public ClassLoader getContextClassLoader()
{
return (ClassLoader) AccessController.doPrivileged(getTCLPrivilegedAction);
}
public ClassLoader getContextClassLoader(final Thread thread)
{
return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
return thread.getContextClassLoader();
}
});
}
public void setContextClassLoader(final ClassLoader cl)
{
AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
Thread.currentThread().setContextClassLoader(cl);
return null;
}
});
}
public void setContextClassLoader(final Thread thread, final ClassLoader cl)
{
AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
thread.setContextClassLoader(cl);
return null;
}
});
}
};
ClassLoader getContextClassLoader();
ClassLoader getContextClassLoader(Thread thread);
void setContextClassLoader(ClassLoader cl);
void setContextClassLoader(Thread thread, ClassLoader cl);
}
/**
* 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 = support.getServer().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 = support.getServer().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;
}
}
}