/**
* 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: JettyDeployer.java 5369 2010-02-24 14:58:19Z benoitf $
* --------------------------------------------------------------------------
*/
package org.ow2.easybeans.deployer;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.List;
import javax.servlet.ServletContextEvent;
import org.ow2.easybeans.deployment.api.EZBInjectionHolder;
import org.ow2.util.archive.api.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 Jetty. <br />
* It will deploy EJB3 and EAR. EJB3 will be deployed in EasyBeans while WAR
* file will go in Jetty.
* @author Florent Benoit
*/
public class JettyDeployer extends AbsWebContainerDeployer implements IDeployer {
/**
* Logger.
*/
private static Log logger = LogFactory.getLog(JettyDeployer.class);
/**
* Link to the Jetty Server used to deploy the web applications.
* Static as shared by all jetty deployers.
*/
private static Object jettyServer = null;
/**
* Name of the WebAppContext class used to deploy a war file.
*/
private static final String WEB_APP_CONTEXT_CLASS_NAME = "org.mortbay.jetty.webapp.WebAppContext";
/**
* WebAppContext class used to deploy a war file.
*/
private Class webAppContextClass = null;
/**
* Name of the method used to get the child handler on the server object.
*/
private static final String GET_CHILD_HANDLER_METHOD_NAME = "getChildHandlerByClass";
/**
* Name of the class that represent an handler collection.
*/
private static final String HANDLER_COLLECTION_CLASS_NAME = "org.mortbay.jetty.handler.HandlerCollection";
/**
* List of the contexts deployed on the current jetty server.
*/
private Object contexts = null;
/**
* Name of the method used to set the URL of the war file on the web app context.
*/
private static final String SET_WAR_METHOD_NAME = "setWar";
/**
* Method object used to set the URL of the war file on the web app context.
*/
private Method setWarMethod = null;
/**
* Name of the method used to set the name of the context on the web app context.
*/
private static final String SET_CONTEXT_METHOD_NAME = "setContextPath";
/**
* Method object used to set the name of the context on the web app context.
*/
private Method setContextPathMethod = null;
/**
* Name of the method used to get the name of the context on the web app context.
*/
private static final String GET_CONTEXT_METHOD_NAME = "getContextPath";
/**
* Method object used to get the name of the context on the web app context.
*/
private Method getContextPathMethod = null;
/**
* Name of the method used to start the web app context.
*/
private static final String START_CONTEXT_METHOD_NAME = "start";
/**
* Method object used to start the web app context.
*/
private Method startContextMethod = null;
/**
* Name of the method used to stop the web app context.
*/
private static final String STOP_CONTEXT_METHOD_NAME = "stop";
/**
* Method object used to stop the web app context.
*/
private Method stopContextMethod = null;
/**
* Name of the class that is an handler.
*/
private static final String HANDLER_CLASS_NAME = "org.mortbay.jetty.Handler";
/**
* Name of the method used to add an handler.
*/
private static final String ADD_HANDLER_METHOD_NAME = "addHandler";
/**
* Method object used to add an handler.
*/
private Method addHandlerMethod = null;
/**
* Name of the method used to remove an handler.
*/
private static final String REMOVE_HANDLER_METHOD_NAME = "removeHandler";
/**
* Method object used to remove an handler.
*/
private Method removeHandlerMethod = null;
/**
* Name of the method used to get all handlers.
*/
private static final String GET_ALL_HANDLER_METHOD_NAME = "getHandlers";
/**
* Method object used to get all handlers.
*/
private Method getHandlersMethod = null;
/**
* Name of the method for changing the Java Delegation model.
*/
private static final String SET_JAVA_DELEGATION_MODEL_METHOD_NAME = "setParentLoaderPriority";
/**
* Method object used for changing the Java Delegation model.
*/
private Method javaDelegationModelMethod = null;
/**
* Build a new instance of this deployer.
* @throws DeployerException if the instance is not built.
*/
public JettyDeployer() throws DeployerException {
super();
// Check if this deployer has been initialized correctly
if (jettyServer == null) {
throw new DeployerException("This deployer has not be initialized correctly as the Jetty server is null");
}
// Load the class
this.webAppContextClass = loadClass(WEB_APP_CONTEXT_CLASS_NAME, getClassLoader());
// get Handler collection from jetty
Method getChildHandlerByClassMethod = getMethod(jettyServer.getClass(), GET_CHILD_HANDLER_METHOD_NAME, Class.class);
Class handlerCollectionClass = loadClass(HANDLER_COLLECTION_CLASS_NAME, getClassLoader());
Object handlerCollection = invoke(getChildHandlerByClassMethod, jettyServer, handlerCollectionClass);
if (handlerCollection == null) {
throw new DeployerException("No handler collection has been defined on the jetty server object '" + jettyServer
+ "'.");
}
this.contexts = invoke(getChildHandlerByClassMethod, handlerCollection, handlerCollectionClass);
// add/remove/get Handler method
Class handlerClazz = loadClass(HANDLER_CLASS_NAME, getClassLoader());
this.addHandlerMethod = getMethod(this.contexts.getClass(), ADD_HANDLER_METHOD_NAME, handlerClazz);
this.removeHandlerMethod = getMethod(this.contexts.getClass(), REMOVE_HANDLER_METHOD_NAME, handlerClazz);
this.getHandlersMethod = getMethod(this.contexts.getClass(), GET_ALL_HANDLER_METHOD_NAME);
// get the setters methods
this.setWarMethod = getMethod(this.webAppContextClass, SET_WAR_METHOD_NAME, String.class);
this.setContextPathMethod = getMethod(this.webAppContextClass, SET_CONTEXT_METHOD_NAME, String.class);
this.javaDelegationModelMethod = getMethod(this.webAppContextClass, SET_JAVA_DELEGATION_MODEL_METHOD_NAME, boolean.class);
// getters
this.getContextPathMethod = getMethod(this.webAppContextClass, GET_CONTEXT_METHOD_NAME);
// and the start method
this.startContextMethod = getMethod(this.webAppContextClass, START_CONTEXT_METHOD_NAME);
this.stopContextMethod = getMethod(this.webAppContextClass, STOP_CONTEXT_METHOD_NAME);
}
/**
* 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
* @param ejbInjectionHolder the EasyBeans injection holder object
* @throws DeployerException if the wars are not deployed.
*/
@Override
protected void deployWARs(final EARDeployable earDeployable, final URL earURL, final ClassLoader earClassLoader,
final ClassLoader parentClassLoader, final EZBInjectionHolder ejbInjectionHolder) 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 instance of the webapp context
Object webAppContext = newInstance(this.webAppContextClass);
// invoke setters
// for the URL of the war file
try {
invoke(this.setWarMethod, webAppContext, war.getArchive().getURL().getPath());
} catch (ArchiveException e) {
throw new DeployerException("Cannot get URL from the archive '" + war.getArchive() + "'.", e);
}
// for defining the name of the context
invoke(this.setContextPathMethod, webAppContext, "/" + war.getContextRoot());
// Java delegation model = true
invoke(this.javaDelegationModelMethod, webAppContext, Boolean.TRUE);
// add the built context on the existing list
invoke(this.addHandlerMethod, this.contexts, webAppContext);
// set the thread context classloader to the parent classloader
ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(parentClassLoader);
// start context
try {
invoke(this.startContextMethod, webAppContext);
} finally {
// reset classloader
Thread.currentThread().setContextClassLoader(oldCl);
}
// War has been deployed
logger.info("The war ''{0}'' has been deployed on the ''{1}'' context.", war, war.getContextRoot());
}
}
/**
* @return the classloader for loading the class.
*/
private static ClassLoader getClassLoader() {
return jettyServer.getClass().getClassLoader();
}
/**
* 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();
// Get all handlers
Object[] handlers = (Object[]) invoke(this.getHandlersMethod, this.contexts);
// For each handler, check the name of the context root
Object context = null;
for (Object handler : handlers) {
// Handler is a webapp context ?
if (handler.getClass().equals(this.webAppContextClass)) {
// get context
String foundCtx = (String) invoke(this.getContextPathMethod, handler);
if (contextRoot.equals(foundCtx) || ("/" + contextRoot).equals(foundCtx)) {
// found !
context = handler;
break;
}
}
}
// Context not found
if (context == null) {
throw new DeployerException("Unable to find a context with the name '" + contextRoot
+ "' for the War deployable '" + warDeployable + "'.");
}
// Stop the context
invoke(this.stopContextMethod, context);
// Remove the context
invoke(this.removeHandlerMethod, this.contexts, context);
logger.info("The context ''{0}'' of the War ''{1}'' has been undeployed", contextRoot, warDeployable);
}
/**
* Set the given context event object.
* @param contextEvent the given object.
* @throws DeployerException if the jetty server object is not found from the given context event.
*/
public static void setContextEvent(final ServletContextEvent contextEvent) throws DeployerException {
// First, try to get the source on the given context event
Object source = contextEvent.getSource();
if (source == null) {
throw new DeployerException("No source object on the given contextEvent '" + contextEvent + "'.");
}
// try to get getContextHandler method on this source object
Method getContextHandlerMethod = getMethod(source.getClass(), "getContextHandler");
if (getContextHandlerMethod == null) {
throw new DeployerException("No getContextHandler method was found on the '" + source + "' object");
}
// get Handler
Object contextHandler = invoke(getContextHandlerMethod, source);
if (contextHandler == null) {
throw new DeployerException("No context handler object was returned from the '" + source + "' object");
}
// get the getServer method
Method getServerMethod = getMethod(contextHandler.getClass(), "getServer");
if (getServerMethod == null) {
throw new DeployerException("No getServer method was found on the '" + contextHandler + "' object");
}
// Set the jetty server
jettyServer = invoke(getServerMethod, contextHandler);
// No server ?
if (jettyServer == null) {
throw new DeployerException("No Jetty server found on the servlet context event '" + contextEvent + "'.");
}
}
}