/**
* EasyBeans
* Copyright (C) 2006-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: EasyBeansContextListener.java 5369 2010-02-24 14:58:19Z benoitf $
* --------------------------------------------------------------------------
*/
package org.ow2.easybeans.server.war;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* Listener class which is notified of lifecycle events. It allows to start
* EasyBeans at startup and stop it at the end.
* @author Florent Benoit
*/
public class EasyBeansContextListener implements ServletContextListener {
/**
* Resource containing the list of files.
*/
private static final String COMMON_LIBRARIES_LIST = "easybeans-exported-libraries.lst";
/**
* Default XML file.
*/
public static final String DEFAULT_XML_FILE = "org/ow2/easybeans/server/war/easybeans-default.xml";
/**
* Default Bootstrap class to instantiate.
*/
public static final String DEFAULT_BOOTSTRAP_CLASS = "org.ow2.easybeans.server.war.EmbeddedBootstrap";
/**
* Embedded bootstrap instance.
*/
private Object embeddedObject = null;
/**
* Bootstrap class.
*/
private Class<?> bootStrapClass;
/**
* Container detected ?.
*/
private ContainerTypeEnum containerType;
/**
* Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
* @param servletContextEvent event class for notifications about changes to
* the servlet context of a web application.
*/
public void contextInitialized(final ServletContextEvent servletContextEvent) {
// Create and configure EasyBeans embedded server
if (this.embeddedObject == null) {
// Detects Tomcat/Jetty/etc.
detectContainerType();
// Analyze some parameters
ServletContext ctx = servletContextEvent.getServletContext();
boolean enableSmartFactory = Boolean.parseBoolean(ctx.getInitParameter("enableSmartFactory"));
// Register in server some libraries
registerLibraries(servletContextEvent);
try {
// Get class
this.bootStrapClass = Thread.currentThread().getContextClassLoader().loadClass(getBootstrapClassName());
this.embeddedObject = this.bootStrapClass.newInstance();
// Configure container type
Method setContTypeMethod = this.bootStrapClass.getMethod("setContainerType", ContainerTypeEnum.class);
setContTypeMethod.invoke(this.embeddedObject, this.containerType);
// Configure context event
Method setServletContextEventMethod = this.bootStrapClass.getMethod("setServletContextEvent",
ServletContextEvent.class);
setServletContextEventMethod.invoke(this.embeddedObject, servletContextEvent);
// Sets the parent directory
String webContainerDirectory = getWebContainerDirectory();
System.setProperty("easybeans.home", webContainerDirectory);
// Use of smart factory
Method setEnableSmartFactoryMethod = this.bootStrapClass.getMethod("setEnableSmartFactory",
boolean.class);
setEnableSmartFactoryMethod.invoke(this.embeddedObject, Boolean.valueOf(enableSmartFactory));
// Start
Method startMethod = this.bootStrapClass.getMethod("start");
startMethod.invoke(this.embeddedObject);
} catch (Exception e) {
e.printStackTrace();
throw new IllegalStateException("Cannot start", e);
}
}
}
/**
* Notification that the servlet context is about to be shut down. All
* servlets and filters have been destroy()ed before any
* ServletContextListeners are notified of context destruction.
* @param servletContextEvent event class for notifications about changes to
* the servlet context of a web application.
*/
public void contextDestroyed(final ServletContextEvent servletContextEvent) {
if (this.embeddedObject != null) {
try {
Method stopMethod = this.bootStrapClass.getMethod("stop");
stopMethod.invoke(this.embeddedObject);
} catch (Exception e) {
throw new IllegalStateException("Cannot stop", e);
}
}
// Run GC
System.gc();
}
/**
* Detect the web Container type.
*/
protected void detectContainerType() {
// catalina base ?
String catalinaBase = System.getProperty("catalina.base");
if (catalinaBase != null) {
// Detect tomcat 5.5 or 6.0
try {
EasyBeansContextListener.class.getClassLoader().loadClass("org.apache.AnnotationProcessor");
// Class present, tomcat6 case
this.containerType = ContainerTypeEnum.TOMCAT6;
return;
} catch (ClassNotFoundException e) {
// Not found, tomcat5 case
this.containerType = ContainerTypeEnum.TOMCAT5;
return;
}
}
// Jetty ?
String jettyHome = System.getProperty("jetty.home");
if (jettyHome != null) {
// Detect Jetty 6 or 7.0
try {
EasyBeansContextListener.class.getClassLoader().loadClass("org.eclipse.jetty.webapp.WebAppContext");
// Class present, Jetty7 case
this.containerType = ContainerTypeEnum.JETTY7;
return;
} catch (ClassNotFoundException e) {
// Not found, jetty6 case
this.containerType = ContainerTypeEnum.JETTY6;
return;
}
}
// not found, return tmp directory
this.containerType = ContainerTypeEnum.UNKNOWN;
}
/**
* Gets the directory for deployables depending on the servlet container.
* @return path to catalina.base/jetty.home/etc.
*/
private String getWebContainerDirectory() {
// catalina base ?
String catalinaBase = System.getProperty("catalina.base");
if (catalinaBase != null) {
return catalinaBase;
}
// Jetty ?
String jettyHome = System.getProperty("jetty.home");
if (jettyHome != null) {
return jettyHome;
}
// not found, return tmp directory
return System.getProperty("java.io.tmpdir");
}
/**
* Register the EasyBeans libraries in the root classloader (allowing each
* applications to communicate with EasyBeans).
* @param servletContextEvent event class for notifications about changes to
* the servlet context of a web application.
*/
private void registerLibraries(final ServletContextEvent servletContextEvent) {
if (this.containerType.equals(ContainerTypeEnum.TOMCAT5) || this.containerType.equals(ContainerTypeEnum.TOMCAT6)) {
registerTomcat(servletContextEvent);
} else if (this.containerType.equals(ContainerTypeEnum.JETTY6) || this.containerType.equals(ContainerTypeEnum.JETTY7)) {
registerJetty(servletContextEvent);
}
}
/**
* Register the EasyBeans libraries for Jetty.
* @param servletContextEvent event class for notifications about changes to
* the servlet context of a web application.
*/
protected void registerJetty(final ServletContextEvent servletContextEvent) {
// Current Classloader
ClassLoader currentCL = Thread.currentThread().getContextClassLoader();
// Get parent
ClassLoader cl = currentCL.getParent();
// Add libraries
addLib(cl, servletContextEvent);
}
/**
* Register the EasyBeans libraries for Tomcat.
* @param servletContextEvent event class for notifications about changes to
* the servlet context of a web application.
*/
protected void registerTomcat(final ServletContextEvent servletContextEvent) {
// Current Classloader
ClassLoader currentCL = Thread.currentThread().getContextClassLoader();
// Now, search shared classloader (child of server CL)
// Assume that it is a correct Tomcat installation
// For tomcat 6.x, this is only the direct parent while for Tomcat
// 5.5.x, it's parent of parent
ClassLoader cl = currentCL.getParent();
ClassLoader parent2 = cl.getParent();
if ("org.apache.catalina.loader.StandardClassLoader".equals(parent2.getClass().getName())) {
// Tomcat 5.5.x case, gets the parent again
cl = parent2;
}
// Check class :
if (!"org.apache.catalina.loader.StandardClassLoader".equals(cl.getClass().getName())) {
throw new IllegalStateException("Didn't find common classloader");
}
// Add libraries
addLib(cl, servletContextEvent);
}
/**
* Add libraries in the given classloader.
* @param cl the classloader where to add jars (which needs to be an
* URLClassLoader)
* @param servletContextEvent the servlet context event
*/
protected void addLib(final ClassLoader cl, final ServletContextEvent servletContextEvent) {
// Get URL ClassLoader
URLClassLoader urlCl = null;
if (cl instanceof URLClassLoader) {
urlCl = (URLClassLoader) cl;
} else {
throw new IllegalStateException("Parent classloader is not an URL classloader. Found '" + cl + "'.");
}
// Get addURL method
Method addURL = null;
try {
addURL = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] {URL.class});
} catch (SecurityException e) {
throw new IllegalArgumentException("Cannot get addURL method on the class '" + URLClassLoader.class + "'.", e);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Cannot get addURL method on the class '" + URLClassLoader.class + "'.", e);
}
addURL.setAccessible(true);
// Add libraries
ServletContext sContext = servletContextEvent.getServletContext();
List<String> libraries = new ArrayList<String>();
getLibraries(sContext, libraries);
for (String library : libraries) {
URL url = null;
try {
url = new File(library).toURI().toURL();
} catch (Exception e) {
throw new IllegalArgumentException("Cannot get URL on file '" + library + "'.", e);
}
try {
// logger.debug("Registering '" + url + "'.");
addURL.invoke(urlCl, url);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Cannot register the URL '" + url + "'.", e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot register the URL '" + url + "'.", e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("Cannot register the URL '" + url + "'.", e);
}
}
addURL.setAccessible(false);
}
/**
* Retrieve a List of libraries to be loaded by TopLevel ClassLoaders.
* @param sContext ServletContext.
* @param libraries List of libs.
*/
protected void getLibraries(final ServletContext sContext, final List<String> libraries) {
// Files that needs to be in the commons classloader are stored in a
// properties file.
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
List<String> librariesList = getExportedLibraries();
for (String library : librariesList) {
URL url = classLoader.getResource(library);
if (url == null) {
throw new IllegalStateException("No resource named '" + library + "' was found in the current classloader.");
}
// got url, open it
URLConnection urlConnection = null;
try {
urlConnection = url.openConnection();
} catch (IOException e) {
throw new IllegalStateException("Cannot open connection on the URL '" + url + "'.", e);
}
urlConnection.setDefaultUseCaches(false);
Reader reader = null;
try {
try {
reader = new InputStreamReader(urlConnection.getInputStream());
} catch (IOException e) {
throw new IllegalStateException("Cannot get inputstream on the URL '" + url + "'.", e);
}
BufferedReader bufferedReader = new BufferedReader(reader);
String fileName = null;
try {
while ((fileName = bufferedReader.readLine()) != null) {
fileName = fileName.trim();
// only lines with values
if (fileName.length() >= 0) {
// Get filename
libraries.add(sContext.getRealPath("/WEB-INF/lib/" + fileName.trim()));
}
}
} catch (IOException e) {
throw new IllegalStateException("Cannot read the inputstream on the URL '" + url + "'.", e);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
throw new IllegalStateException("Cannot close reader object on the URL '" + url + "'.", e);
}
}
}
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new IllegalStateException("Cannot close reader object on the URL '" + url + "'.", e);
}
}
}
}
}
/**
* @return the bootstrap class name to use.
*/
protected String getBootstrapClassName() {
return DEFAULT_BOOTSTRAP_CLASS;
}
/**
* @return a List of resources that contains the libraries to be exported.
*/
protected List<String> getExportedLibraries() {
List<String> list = new ArrayList<String>();
list.add(COMMON_LIBRARIES_LIST);
return list;
}
}