/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, 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.ejb3.client;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.rmi.server.RMIClassLoader;
import java.rmi.server.RMIClassLoaderSpi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.jboss.beans.metadata.plugins.builder.BeanMetaDataBuilderFactory;
import org.jboss.beans.metadata.spi.BeanMetaData;
import org.jboss.beans.metadata.spi.BeanMetaDataFactory;
import org.jboss.beans.metadata.spi.ValueMetaData;
import org.jboss.beans.metadata.spi.builder.BeanMetaDataBuilder;
import org.jboss.classloader.plugins.system.DefaultClassLoaderSystem;
import org.jboss.classloader.spi.ClassLoaderDomain;
import org.jboss.classloader.spi.ClassLoaderSystem;
import org.jboss.classloader.spi.ParentPolicy;
import org.jboss.classloading.spi.dependency.ClassLoading;
import org.jboss.classloading.spi.vfs.dependency.VFSClassLoaderPolicyModule;
import org.jboss.classloading.spi.vfs.metadata.VFSClassLoaderFactory;
import org.jboss.classloading.spi.vfs.metadata.VFSClassLoaderFactory10;
import org.jboss.client.AppClientLauncher;
import org.jboss.dependency.spi.ControllerMode;
import org.jboss.dependency.spi.ControllerState;
import org.jboss.deployment.dependency.JndiDependencyMetaData;
import org.jboss.kernel.Kernel;
import org.jboss.kernel.plugins.bootstrap.AbstractBootstrap;
import org.jboss.kernel.plugins.bootstrap.basic.BasicBootstrap;
import org.jboss.kernel.plugins.deployment.AbstractKernelDeployment;
import org.jboss.kernel.plugins.deployment.xml.BasicXMLDeployer;
import org.jboss.kernel.spi.dependency.KernelController;
import org.jboss.kernel.spi.dependency.KernelControllerContext;
import org.jboss.kernel.spi.deployment.KernelDeployment;
import org.jboss.logging.Logger;
import org.jboss.metadata.client.jboss.JBossClientMetaData;
import org.jboss.remoting.Remoting;
import org.jboss.vfs.VFS;
import org.jboss.vfs.VirtualFile;
import org.jboss.vfs.util.PathTokenizer;
import org.jboss.vfs.util.automount.Automounter;
import org.jboss.vfs.util.automount.MountOption;
import org.jboss.xb.binding.JBossXBException;
/**
* This class launches a JavaEE 5 application client.
*
* The first argument is either a jar file containing the client deployment files or the application client class name.
* The manifest file Main-Class attribute must point to the application client class.
* It must also contain an application client deployment descriptor file (META-INF/application-client.xml).
*
* @author <a href="mailto:carlo.dewolf@jboss.com">Carlo de Wolf</a>
* @author Scott.Stark@jboss.org
* @version $Revision: 110199 $
*/
public class ClientLauncher
implements AppClientLauncher
{
private static final Logger log = Logger.getLogger(ClientLauncher.class);
private static final String VERSION = "$Revision: 110199 $";
private static Throwable exception;
/** The kernel for the client container */
private static Kernel kernel;
/** The deployer */
private static BasicXMLDeployer deployer;
/** The mainClass used by the ClientContainer */
private static Class<?> theMainClass;
/** Should the remoting class loader delegate to the tcl */
private static boolean remotingDelgatesToTCL = true;
/** Additional classpath elements to client container classpath */
private static List<String> extraClassPath = new ArrayList<String>();
/**
* Access the client main class as loaded by the MC/client container. This
* class should be used to ensure that any static variable set in the class
* as a by-product of invoking the client main(String[]) method are seen.
* @return client main class object
*/
public static Class<?> getTheMainClass()
{
return theMainClass;
}
/**
* Convenience method to load the XML descriptor for the given applicationClientName.
* This does a lookup against the server for the xml metadata the client
* deployer bound.
*
* @return the JBossClientMetaData
* @throws IOException
* @throws JBossXBException
*/
public static JBossClientMetaData loadXML(String applicationClientName) throws NamingException
{
log.warn("FIXME: using an unsupported hack to get metadata");
InitialContext ctx = new InitialContext();
JBossClientMetaData metaData = (JBossClientMetaData) ctx.lookup(applicationClientName + "/metaData");
return metaData;
}
public static List<String> loadClassPath(String applicationClientName)
throws NamingException
{
InitialContext ctx = new InitialContext();
List<String> cp = (List<String>) ctx.lookup(applicationClientName + "/classPathEntries");
return cp;
}
/**
* Calls {@link #launch(String, String, String[], Properties)}
*/
public void launch(String clientClass, String clientName, String[] args)
throws Throwable
{
launch(clientClass, clientName, args, null);
}
/**
* Convenience method for launching a client container.
*
* @param xml
* @param mainClassName
* @param applicationClientName
* @param args
* @throws Exception
*/
public static void launch(JBossClientMetaData xml, String mainClassName, String applicationClientName, String args[])
throws Throwable
{
List<String> cp = Collections.emptyList();
launch(xml, cp, mainClassName, applicationClientName, args, null);
}
/**
* The AppClientLauncher method for launching a client container.
*
* @param mainClassName - the class whose main(String[]) will be invoked
* @param clientName - the client name that maps to the server side JNDI ENC
* @param args - the args to pass to main method
* @param jndiEnv - jndi properties to pass to set as System properties
* @throws Throwable
*/
public static void launch(String mainClassName, String clientName, String args[],
Properties jndiEnv)
throws Throwable
{
// Set the RMIClassLoaderSpi implementation to JBossRMIClassLoader
System.setProperty("java.rmi.server.RMIClassLoaderSpi", JBossRMIClassLoader.class.getName());
// This is loaded lazily via a jndi dependency
JBossClientMetaData xml = null;
List<String> cp = loadClassPath(clientName);
launch(xml, cp, mainClassName, clientName, args, jndiEnv);
}
/**
* The client launcher entry point that create an mc to launch the client container.
* @param clientClass
* @param clientName
* @param cp
* @param args
* @throws Throwable
*/
public static void launch(JBossClientMetaData xml, List<String> classPath,
String mainClassName, String applicationClientName, String args[],
Properties jndiEnv)
throws Throwable
{
log.info("ClientLauncher, version: "+VERSION);
// Init the kernel and deployers
args = init(args);
// Pass in the jndi env properties so InitialContext() works
if(jndiEnv != null)
{
for(Object key : jndiEnv.keySet())
{
String name = (String) key;
System.setProperty(name, jndiEnv.getProperty(name));
}
}
// Have the remoting class loader delegate to the tcl
if(remotingDelgatesToTCL)
System.setProperty(Remoting.CLASSLOADING_PARENT_FIRST_DELEGATION_PROP, "false");
// Setup the
ArrayList<BeanMetaDataFactory> beanFactories = new ArrayList<BeanMetaDataFactory>();
ArrayList<BeanMetaData> beans = new ArrayList<BeanMetaData>();
// Add the common launcher beans, ClassLoaderSystem
BeanMetaDataBuilder builder = BeanMetaDataBuilderFactory.createBuilder("ClassLoaderSystem", ClassLoaderSystem.class.getName());
builder.setFactoryClass(ClientLauncher.class.getName());
builder.setFactoryMethod("getClassLoaderSystem");
BeanMetaData classLoaderSystemBMD = builder.getBeanMetaData();
addBeanMetaData(beanFactories, beans, classLoaderSystemBMD);
// ClassLoading
builder = BeanMetaDataBuilderFactory.createBuilder("ClassLoading", ClassLoading.class.getName());
builder.addMethodInstallCallback("addModule", ControllerState.CONFIGURED);
builder.addMethodUninstallCallback("removeModule", ControllerState.CONFIGURED);
BeanMetaData classLoadingBMD = builder.getBeanMetaData();
addBeanMetaData(beanFactories, beans, classLoadingBMD);
try
{
builder = BeanMetaDataBuilderFactory.createBuilder("ClientContainer",
"org.jboss.ejb3.client.ClientContainer");
VFSClassLoaderFactory factory = new VFSClassLoaderFactory("ClientLauncherClassPath");
ArrayList<String> roots = new ArrayList<String>();
// Create the classpath
log.info("Setting up classpath from: ");
for(String path : classPath)
{
log.info(path);
mount(path);
roots.add(path);
}
for(String path : extraClassPath)
{
log.info(path);
mount(path);
roots.add(path);
}
log.info("End classpath");
factory.setRoots(roots);
beanFactories.add(factory);
// ClientContainer(xml, mainClass, applicationClientName, jndiEnv);
String classLoaderName = factory.getContextName();
if(classLoaderName == null)
classLoaderName = factory.getName() + ":" + factory.getVersion();
String metaDataJndiName = applicationClientName + "/metaData";
ValueMetaData xmlMD = new JndiDependencyValueMetaData(metaDataJndiName, jndiEnv, classLoaderName);
builder.addConstructorParameter(JBossClientMetaData.class.getName(), xmlMD);
//builder.addConstructorParameter(JBossClientMetaData.class.getName(), xml);
builder.addConstructorParameter(Class.class.getName(), mainClassName);
builder.addConstructorParameter(String.class.getName(), applicationClientName);
builder.addConstructorParameter(Properties.class.getName(), jndiEnv);
// Use vfs class loader as the ClientContainer class loader
ValueMetaData classLoader = builder.createInject(classLoaderName);
builder.setClassLoader(classLoader);
BeanMetaData clientContainerMD = builder.getBeanMetaData();
AbstractKernelDeployment deployment = new AbstractKernelDeployment();
deployment.setName(factory.getName() + ":" + factory.getVersion());
addBeanMetaData(beanFactories, beans, clientContainerMD);
deployment.setBeanFactories(beanFactories);
if(beans.size() > 0)
deployment.setBeans(beans);
deploy(deployment);
validate();
KernelController controller = kernel.getController();
// ClientContainer
KernelControllerContext context = (KernelControllerContext) controller.getContext("ClientContainer", ControllerState.INSTALLED);
if (context == null)
throw new Exception("ClientContainer bean was not created");
Object client = context.getTarget();
KernelControllerContext cclContext = (KernelControllerContext) controller.getContext(classLoaderName, ControllerState.INSTALLED);
if (cclContext == null)
throw new Exception(classLoaderName+" bean was not created");
ClassLoader ccLoader = (ClassLoader) cclContext.getTarget();
if (ccLoader == null )
throw new Exception(classLoaderName+" bean was not created");
if (client.getClass().getClassLoader() != ccLoader)
log.warn(client.getClass().getClassLoader()+" != "+ccLoader);
Class<?> clientContainerClass = ccLoader.loadClass("org.jboss.ejb3.client.ClientContainer");
if (clientContainerClass.getClassLoader() != ccLoader)
log.warn(clientContainerClass.getClassLoader()+" != "+ccLoader);
// Invoke main on the underlying client main class through the ClientContainer
ClassLoader prevLoader = Thread.currentThread().getContextClassLoader();
try
{
// Get the mainClass
Class<?> empty[] = {};
Method getMainClass = clientContainerClass.getDeclaredMethod("getMainClass", empty);
theMainClass = (Class<?>) getMainClass.invoke(client, null);
// Invoke main
Thread.currentThread().setContextClassLoader(ccLoader);
Class<?> parameterTypes[] = { args.getClass() };
Method invokeMain = clientContainerClass.getDeclaredMethod("invokeMain", parameterTypes);
invokeMain.invoke(client, (Object) args);
}
finally
{
Thread.currentThread().setContextClassLoader(prevLoader);
}
//
undeploy(deployment);
}
catch(Throwable e)
{
exception = e;
throw e;
}
}
/**
* Create a ClassLoaderSystem with the default ClassLoaderDomain set to use
* a AFTER ParentPolicy.
*
* @return ClassLoaderSystem instance
*/
public static ClassLoaderSystem getClassLoaderSystem()
{
DefaultClassLoaderSystem system = new DefaultClassLoaderSystem();
ClassLoaderDomain defaultDomain = system.getDefaultDomain();
defaultDomain.setParentPolicy(ParentPolicy.AFTER);
return system;
}
private static void addBeanMetaData(
ArrayList<BeanMetaDataFactory> beanFactories,
ArrayList<BeanMetaData> beans, BeanMetaData bmd)
{
// TODO Auto-generated method stub
if(bmd instanceof BeanMetaDataFactory)
{
BeanMetaDataFactory bmdf = (BeanMetaDataFactory) bmd;
beanFactories.add(bmdf);
}
else
{
// Have to use the deprecated beans
beans.add(bmd);
}
}
/**
* Initialize the mc kernel and deployer as well as extract any client
* launcher specific args from the input arguments.
*
* @param args - the input args to both the launcher and the client main. The
* launcher specific arguments are:
* -remotingDelgatesToTCL : true if remoting should first delegate to the the tcl based class loader
* -extraClassPath : comma separated list of vfsurls for additional classpath
* @return the remaining arguments to pass to the client main.
* @throws Throwable
*/
private static String[] init(String[] args) throws Throwable
{
// Extract any launcher args from the input
String[] newArgs = parseArgs(args);
// Bootstrap the kernel
AbstractBootstrap bootstrap = new BasicBootstrap();
bootstrap.run();
kernel = bootstrap.getKernel();
// Create the deployer
deployer = createDeployer();
return newArgs;
}
/**
* Extract the launcher specific arguments from the arguments array.
* @see #init(String[])
* @param args - the input args to both the launcher and the client main.
* @return the remaining arguments to pass to the client main.
*/
private static String[] parseArgs(String[] args)
{
ArrayList<String> tmp = new ArrayList<String>();
for(int n = 0; n < args.length; n ++)
{
String arg = args[n];
if(arg.equalsIgnoreCase("-remotingDelgatesToTCL"))
{
remotingDelgatesToTCL = Boolean.parseBoolean(args[++ n]);
}
else if(arg.equalsIgnoreCase("-extraClassPath"))
{
// Split classpath elements based on ','
String cparg = args[++ n];
String[] cp = cparg.split(",");
for(String path : cp)
{
extraClassPath.add(path);
}
log.debug("Set extraClassPath to: "+extraClassPath);
}
else
{
tmp.add(arg);
}
}
String[] newArgs = new String[tmp.size()];
tmp.toArray(newArgs);
return newArgs;
}
private static BasicXMLDeployer createDeployer()
{
return new BasicXMLDeployer(kernel, ControllerMode.AUTOMATIC);
}
/**
* Deploy a deployment
*
* @param deployment the deployment
* @throws Exception for any error
*/
private static void deploy(KernelDeployment deployment) throws Exception
{
log.debug("Deploying " + deployment);
try
{
deployer.deploy(deployment);
log.debug("Deployed " + deployment);
}
catch (Exception e)
{
throw e;
}
catch (Error e)
{
throw e;
}
catch (Throwable t)
{
throw new RuntimeException("Error deploying deployment: " + deployment, t);
}
}
/**
* Undeploy a deployment
*
* @param deployment the deployment
*/
private static void undeploy(KernelDeployment deployment)
{
log.debug("Undeploying " + deployment.getName());
try
{
deployer.undeploy(deployment);
log.trace("Undeployed " + deployment.getName());
}
catch (Throwable t)
{
log.warn("Error during undeployment: " + deployment.getName(), t);
}
}
/**
* Validate
*
* @throws Exception for any error
*/
private static void validate() throws Exception
{
try
{
deployer.validate();
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw e;
}
catch (Error e)
{
throw e;
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
public static void main(String[] args)
{
try
{
String path = "vfs:///home/carlo/work/jboss-head/build/target/jboss-6.0.0.Final/server/cts/tmp/jsr88/ejb3_timer_basic_mdb.ear/ejb3_timer_basic_mdb_client.jar";
mount(path);
}
catch(Exception e)
{
e.printStackTrace();
}
}
private static void mount(String path) throws IOException, URISyntaxException
{
URI root = new URI(path);
VirtualFile file = VFS.getChild(root);
mount(file);
}
private static void mount(VirtualFile target) throws IOException
{
if(target.exists())
{
Automounter.mount(target);
return;
}
mount(target.getParent());
Automounter.mount(target);
}
/**
* RMIClassLoaderSpi that uses the thread context class loader
*
* @author <a href="mailto:adrian.brock@happeningtimes.com">Adrian Brock</a>
* @author Scott.Stark@jboss.org
* @version $Revision: 110199 $
*/
public static class JBossRMIClassLoader
extends RMIClassLoaderSpi
{
// Attributes ----------------------------------------------------
/**
* The JVM implementation (we delegate most work to it)
*/
RMIClassLoaderSpi delegate = RMIClassLoader.getDefaultProviderInstance();
// Constructors --------------------------------------------------
/**
* Required constructor
*/
public JBossRMIClassLoader()
{
}
// RMIClassLoaderSpi Implementation ------------------------------
/*
* Ignore the JVM, use the thread context classloader for proxy caching
*/
public Class<?> loadProxyClass(String codebase, String[] interfaces, ClassLoader ignored)
throws MalformedURLException, ClassNotFoundException
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return delegate.loadProxyClass(codebase, interfaces, loader);
}
/*
* Just delegate
*/
public Class<?> loadClass(String codebase, String name, ClassLoader ignored)
throws MalformedURLException, ClassNotFoundException
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return delegate.loadClass(codebase, name, loader);
}
/*
* Just delegate
*/
public ClassLoader getClassLoader(String codebase)
throws MalformedURLException
{
return delegate.getClassLoader(codebase);
}
/*
* Try to delegate an default to the java.rmi.server.codebase on any
* failure.
*/
public String getClassAnnotation(Class<?> cl)
{
String annotation = null;
try
{
annotation = delegate.getClassAnnotation(cl);
}
catch(Throwable t)
{
// Try the java.rmi.server.codebase property
annotation = System.getProperty("java.rmi.server.codebase");
}
return annotation;
}
}
}