/**
* EasyBeans
* Copyright (C) 2007 Bull S.A.S.
* Contact: easybeans@ow2.org
*
* This library 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 any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id: JOnASDeployer.java 1335 2007-04-24 12:47:31Z benoitf $
* --------------------------------------------------------------------------
*/
package org.ow2.easybeans.deployer;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.ow2.easybeans.jmx.CommonsModelerException;
import org.ow2.easybeans.jmx.CommonsModelerHelper;
import org.ow2.easybeans.jmx.JMXRemoteException;
import org.ow2.easybeans.jmx.MBeanServerHelper;
import org.ow2.easybeans.util.files.FileUtils;
import org.ow2.easybeans.util.files.FileUtilsException;
import org.ow2.easybeans.util.url.URLUtils;
import org.ow2.util.ee.deploy.api.archive.ArchiveException;
import org.ow2.util.ee.deploy.api.deployable.EARDeployable;
import org.ow2.util.ee.deploy.api.deployable.EJBDeployable;
import org.ow2.util.ee.deploy.api.deployable.IDeployable;
import org.ow2.util.ee.deploy.api.deployable.WARDeployable;
import org.ow2.util.ee.deploy.api.deployer.DeployerException;
import org.ow2.util.ee.deploy.api.deployer.IDeployer;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
/**
* Implementation of the Deployer for EasyBeans in Tomcat. <br />
* It will deploy EJB3 and EAR. EJB3 will be deployed in EasyBeans while WAR
* file will go in Tomcat.
* @author Florent Benoit
*/
public class TomcatDeployer extends AbsWebContainerDeployer implements IDeployer {
/**
* Logger.
*/
private static Log logger = LogFactory.getLog(TomcatDeployer.class);
/**
* Name of the Standard Context class.
*/
private static final String CATALINA_CONTEXT_CLASSNAME = "org.apache.catalina.core.StandardContext";
/**
* Class object for the catalina context.
*/
private Class catalinaContextClass = null;
/**
* Name of the start method for the context object.
*/
private static final String START_METHOD_NAME = "start";
/**
* Method for the start of the catalina context.
*/
private Method startContextMethod = null;
/**
* Method name for setting the parent class loader on the context.
*/
private static final String SET_PARENT_CLASSLOADER_METHOD_NAME = "setParentClassLoader";
/**
* Method for setting the parent class loader on the context.
*/
private Method setParentClassLoaderMethod = null;
/**
* Method name for setting the URL to the war file.
*/
private static final String SET_DOC_BASE_METHOD_NAME = "setDocBase";
/**
* Method for setting the URL to the doc base.
*/
private Method setDocBaseMethod = null;
/**
* Method name for setting the context-root of this context.
*/
private static final String SET_PATH_METHOD_NAME = "setPath";
/**
* Method for setting the context-root of this context.
*/
private Method setPathMethod = null;
/**
* Method name for setting the default context xml file.
*/
private static final String SET_DEFAULT_CONTEXT_XML_METHOD_NAME = "setDefaultContextXml";
/**
* Method for setting the default context xml file.
*/
private Method setDefaultContextXmlMethod = null;
/**
* Method name for setting the default web xml file.
*/
private static final String SET_DEFAULT_WEB_XML_METHOD_NAME = "setDefaultWebXml";
/**
* Name of the method for changing the Java Delegation model.
*/
private static final String SET_JAVA_DELEGATION_MODEL_METHOD_NAME = "setDelegate";
/**
* Name of the method for setting the META-INF/context.xml file.
*/
private static final String SET_CONFIG_FILE_METHOD_NAME = "setConfigFile";
/**
* Method object used for changing the Java Delegation model.
*/
private Method setJavaDelegationModelMethod = null;
/**
* Method for setting the default web xml file.
*/
private Method setDefaultWebXmlMethod = null;
/**
* Method for setting the path to META-INF/context.xml file.
*/
private Method setConfigFileMethod = null;
/**
* Engine Object Name.
*/
private static final String ENGINE_OBJECT_NAME = "*:type=Engine";
/**
* Destroy operation on the Tomcat context to undeploy the war.
*/
private static final String DESTROY_OPERATION = "destroy";
/**
* Build a new instance of this deployer.
* @throws DeployerException if the instance is not built.
*/
public TomcatDeployer() throws DeployerException {
super();
// First, load the context class
catalinaContextClass = loadClass(CATALINA_CONTEXT_CLASSNAME);
// get methods for the context
startContextMethod = getMethod(catalinaContextClass, START_METHOD_NAME);
setParentClassLoaderMethod = getMethod(catalinaContextClass, SET_PARENT_CLASSLOADER_METHOD_NAME, ClassLoader.class);
setDocBaseMethod = getMethod(catalinaContextClass, SET_DOC_BASE_METHOD_NAME, String.class);
setPathMethod = getMethod(catalinaContextClass, SET_PATH_METHOD_NAME, String.class);
setDefaultContextXmlMethod = getMethod(catalinaContextClass, SET_DEFAULT_CONTEXT_XML_METHOD_NAME, String.class);
setDefaultWebXmlMethod = getMethod(catalinaContextClass, SET_DEFAULT_WEB_XML_METHOD_NAME, String.class);
setJavaDelegationModelMethod = getMethod(catalinaContextClass, SET_JAVA_DELEGATION_MODEL_METHOD_NAME, boolean.class);
setConfigFileMethod = getMethod(catalinaContextClass, SET_CONFIG_FILE_METHOD_NAME, String.class);
}
/**
* Deploy a deployable. It can be an EJB jar, EAR, WAR, etc.
* @param deployable a given deployable
* @throws DeployerException if the deployment is not done.
*/
public void deploy(final IDeployable deployable) throws DeployerException {
checkSupportedDeployable(deployable);
if (deployable instanceof EJBDeployable) {
deployEJB((EJBDeployable) deployable);
} else if (deployable instanceof EARDeployable) {
// needs to unpack it before deploying it
EARDeployable earDeployable = unpackEARDeployable((EARDeployable) deployable);
deployEAR(earDeployable);
}
}
/**
* Deploy the WAR files present in the given EAR.
* @param earDeployable the EAR containing the WARs
* @param earURL the EAR URL
* @param earClassLoader the EAR classloader
* @param parentClassLoader the parent classloader (EJB) to use
* @throws DeployerException if the wars are not deployed.
*/
@Override
protected void deployWARs(final EARDeployable earDeployable, final URL earURL, final ClassLoader earClassLoader,
final ClassLoader parentClassLoader) throws DeployerException {
// First, try to see if there are .war in this EAR
List<WARDeployable> wars = earDeployable.getWARDeployables();
for (WARDeployable war : wars) {
// Build a new context for this war file
Object ctx = newInstance(catalinaContextClass);
// Build Object Name
String objectName = buildObjectName(war);
// Register Object MBean
try {
CommonsModelerHelper.registerModelerMBean(ctx, objectName);
} catch (CommonsModelerException e) {
throw new DeployerException("Cannot register the object '" + ctx + "' with the objectname '" + objectName
+ "'.", e);
}
// set the context-root
invoke(setPathMethod, ctx, war.getContextRoot());
// Get the URL for this War
URL warURL = null;
try {
warURL = war.getArchive().getURL();
} catch (ArchiveException e) {
throw new DeployerException("Cannot get the URL for the archive '" + war.getArchive() + "'.", e);
}
// File of this war
File warFile = URLUtils.urlToFile(warURL);
// docbase
String docBase = warFile.getPath();
// File and not a directory --> unpack it
if (warFile.isFile()) {
// Need to unpack the file
File unpackDir = unpack(warFile, war, earURL);
docBase = unpackDir.getPath();
}
// set the path to the war file (needs to be unpacked else it will
// be unpacked by tomcat in the webapps folder and it will try to
// deploy twice the webapp with wrong classloader)
invoke(setDocBaseMethod, ctx, docBase);
// default XML files
invoke(setDefaultWebXmlMethod, ctx, "conf/web.xml");
invoke(setDefaultContextXmlMethod, ctx, "context.xml");
File contextXmlFile = new File(docBase + File.separator + "META-INF" + File.separator + "context.xml");
// META-INF/context.xml file support
if (contextXmlFile.exists()) {
invoke(setConfigFileMethod, ctx, contextXmlFile.getAbsolutePath());
}
// Set the parent class loader
invoke(setParentClassLoaderMethod, ctx, parentClassLoader);
// Set the java delegation model
invoke(setJavaDelegationModelMethod, ctx, Boolean.TRUE);
// Invoke init method
// invoke(initContextMethod, ctx);
// Invoke start method
invoke(startContextMethod, ctx);
// War has been deployed
logger.info("The war ''{0}'' has been deployed on the ''{1}'' context.", war, war.getContextRoot());
}
}
/**
* Check that the given deployable is supported by this deployer. If it is
* not supported, throw an error.
* @param deployable the deployable that needs to be deployed
* @throws DeployerException if this deployable is not supported.
*/
private void checkSupportedDeployable(final IDeployable deployable) throws DeployerException {
if (!(deployable instanceof EARDeployable || deployable instanceof EJBDeployable)) {
throw new DeployerException("The deployable '" + deployable + "' is not supported by this deployer");
}
}
/**
* Undeploy an given WAR (called by the undeploy method).
* @param warDeployable a given WAR deployable
* @throws DeployerException if the undeployment is not done.
*/
@Override
protected void undeployWAR(final WARDeployable warDeployable) throws DeployerException {
// get the root context of this deployable
String contextRoot = warDeployable.getContextRoot();
// Now, search the MBean of this context
ObjectName contextObjectName = null;
try {
contextObjectName = new ObjectName(buildObjectName(warDeployable));
} catch (MalformedObjectNameException e) {
throw new DeployerException("Cannot get the ObjectName for the WAR deployable '" + warDeployable + "'.", e);
} catch (NullPointerException e) {
throw new DeployerException("Cannot get the ObjectName for the WAR deployable '" + warDeployable + "'.", e);
}
// Now, search if the MBean of this context is present
try {
if (!MBeanServerHelper.getMBeanServerServer().isRegistered(contextObjectName)) {
throw new DeployerException("There is no MBean with the ObjectName '" + contextObjectName
+ "' in the MBean Server for the WAR deployable '" + warDeployable + "'.");
}
} catch (JMXRemoteException e) {
throw new DeployerException("Cannot check if the MBean with the ObjectName '" + contextObjectName
+ "'is registered in the MBean Server for the WAR deployable '" + warDeployable + "'.");
}
// Undeploy
try {
MBeanServerHelper.getMBeanServerServer().invoke(contextObjectName, DESTROY_OPERATION, null, null);
} catch (InstanceNotFoundException e) {
throw new DeployerException("Cannot remove the context '" + contextRoot + "' of the war deployable '"
+ warDeployable + "'.", e);
} catch (MBeanException e) {
throw new DeployerException("Cannot remove the context '" + contextRoot + "' of the war deployable '"
+ warDeployable + "'.", e);
} catch (ReflectionException e) {
throw new DeployerException("Cannot remove the context '" + contextRoot + "' of the war deployable '"
+ warDeployable + "'.", e);
} catch (JMXRemoteException e) {
throw new DeployerException("Cannot remove the context '" + contextRoot + "' of the war deployable '"
+ warDeployable + "'.", e);
}
logger.info("The context ''{0}'' of the War ''{1}'' has been undeployed", contextRoot, warDeployable);
}
/**
* Build an objectname for the given war.
* @param war the given war deployable that contains the datas.
* @return a JMX object name.
* @throws DeployerException if the object name cannot be built.
*/
private String buildObjectName(final WARDeployable war) throws DeployerException {
String objectName = getDomain() + ":j2eeType=WebModule,name=//" + getDefaultHost() + "/" + war.getContextRoot()
+ ",J2EEServer=EasyBeans,J2EEApplication=EAR";
return objectName;
}
/**
* Gets the JMX domain of Tomcat.
* @return the JMX domain of Tomcat.
* @throws DeployerException if the domain cannot be found
*/
private String getDomain() throws DeployerException {
return getEngineObjectName().getDomain();
}
/**
* @return the first Engine object name found in the MBean server
* @throws DeployerException if the engine object name is not found in
* the MBean server.
*/
private ObjectName getEngineObjectName() throws DeployerException {
// Build Engine object name
ObjectName engineObjectName = null;
try {
engineObjectName = new ObjectName(ENGINE_OBJECT_NAME);
} catch (MalformedObjectNameException e) {
throw new DeployerException("Cannot build Tomcat Engine MBean.", e);
} catch (NullPointerException e) {
throw new DeployerException("Cannot build Tomcat Engine MBean.", e);
}
// Ask the MBean server
Set objectNames = null;
try {
// Get the list
objectNames = MBeanServerHelper.getMBeanServerServer().queryNames(engineObjectName, null);
} catch (JMXRemoteException e) {
throw new DeployerException("Cannot get Tomcat Engine MBean.", e);
}
// Find objects ?
if (objectNames.size() == 0) {
throw new DeployerException("No Tomcat Engine MBean was found in the MBean server");
}
// Return the domain of this object name
return ((ObjectName) objectNames.iterator().next());
}
/**
* @return the default host of the first engine found in the MBean server
* @throws DeployerException if the MBean is not found.
*/
private String getDefaultHost() throws DeployerException {
ObjectName engineObjectName = getEngineObjectName();
try {
return MBeanServerHelper.getMBeanServerServer().getAttribute(engineObjectName, "defaultHost").toString();
} catch (AttributeNotFoundException e) {
throw new DeployerException("Cannot get the default host on the object name '" + engineObjectName + "'.", e);
} catch (InstanceNotFoundException e) {
throw new DeployerException("Cannot get the default host on the object name '" + engineObjectName + "'.", e);
} catch (MBeanException e) {
throw new DeployerException("Cannot get the default host on the object name '" + engineObjectName + "'.", e);
} catch (ReflectionException e) {
throw new DeployerException("Cannot get the default host on the object name '" + engineObjectName + "'.", e);
} catch (JMXRemoteException e) {
throw new DeployerException("Cannot get the default host on the object name '" + engineObjectName + "'.", e);
}
}
/**
* Unpack the given war into a temp directory.
* @param warFile the war to unpack
* @param war data on the given war
* @param earURL url of the EAR that contains the war
* @return the File to the unpack directory
* @throws DeployerException if unpack fails
*/
private File unpack(final File warFile, final WARDeployable war, final URL earURL) throws DeployerException {
// Get EAR application name
String earName = URLUtils.urlToFile(earURL).getName();
// unpack Root directory
String rootUnpackDir = System.getProperty("java.io.tmpdir") + File.separator + System.getProperty("user.name")
+ "-EasyBeans-unpack" + File.separator;
// Unpack directory
File unpackDir = new File(rootUnpackDir, earName + File.separator + warFile.getName());
// Build a JarFile on the war
JarFile packedJar;
try {
packedJar = new JarFile(warFile);
} catch (IOException e) {
throw new DeployerException("The war file '" + warFile + "' is not a valid jar file", e);
}
// Unpack the war
try {
FileUtils.unpack(packedJar, unpackDir);
} catch (FileUtilsException e) {
throw new DeployerException("Cannot unpack the file '" + packedJar + "' in the directory '" + unpackDir + "'.", e);
}
return unpackDir;
}
}
|