blob: 29ec044749064222909a96b825515dee533661f3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.loader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* Boostrap loader for Catalina or other java apps.
*
* This application constructs a class loader
* for use in loading the Catalina internal classes (by accumulating all of the
* JAR files found in the "server" directory under "catalina.home"), and
* starts the regular execution of the container. The purpose of this
* roundabout approach is to keep the Catalina internal classes (and any
* other classes they depend on, such as an XML parser) out of the system
* class path and therefore not visible to application level classes.
*
*
* Merged with CatalinaProperties:
* Load a properties file describing the modules and startup sequence.
* This is responsible for configuration of the loader.
* TODO: support jmx-style configuration, including persistence.
* TODO: better separate legacy config and the new style
*
* The properties file will be named "loader.properties" or
* "catalina.properties" ( for backwad compatibility ) and
* will be searched in:
* - TODO
*
* Properties used:
* - TODO
*
* loader.* and *.loader properties are used internally by the loader (
* *.loader is for backward compat with catalina ).
* All other properties in the config file are set as System properties.
*
* Based on o.a.catalina.bootstrap.CatalinaProperties - utility class to read
* the bootstrap Catalina configuration.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
* @author Costin Manolache
*/
public final class Loader {
private static final boolean DEBUG=true; //LoaderProperties.getProperty("loader.debug.Loader") != null;
// If flat, only one loader is created. If false - one loader per jar/dir
private static final boolean FLAT=false;//LoaderProperties.getProperty("loader.Loader.flat") != null;
// -------------------------------------------------------------- Constants
private static final String CATALINA_HOME_TOKEN = "${catalina.home}";
private static final String CATALINA_BASE_TOKEN = "${catalina.base}";
// ------------------------------------------------------- Static Variables
/**
* Daemon object used by main.
*/
private static Loader daemon = null;
// one should be enough
ModuleListener listener;
// -------------------------------------------------------------- Variables
protected Repository commonRepository = null;
protected Repository catalinaRepository = null;
protected Repository sharedRepository = null;
protected ClassLoader catalinaLoader = null;
private String[] args;
private Hashtable repositories=new Hashtable();
private ClassLoader parentClassLoader;
private static Properties properties = null;
private static String propFile;
// -------------------------------------------------------- Private Methods
/** Set the parent class loader - can be used instead of setParent,
* in case this is the top loader and needs to delagate to embedding app.
* The common loader will delegate to this loader
*
* @param myL
*/
public void setParentClassLoader(ClassLoader myL) {
this.parentClassLoader=myL;
}
/** Initialize the loader, creating all repositories.
* Will create common, server, shared.
*
* TODO: create additional repos.
*
*/
public void init() {
try {
commonRepository = initRepository("common", null, parentClassLoader);
catalinaRepository = initRepository("server", commonRepository,null);
catalinaLoader = catalinaRepository.getClassLoader();
sharedRepository = initRepository("shared", commonRepository,null);
} catch (Throwable t) {
log("Class loader creation threw exception", t);
System.exit(1);
}
}
/** Create a new repository.
* No Module is added ( currently )
* TODO: use props to prepopulate, if any is present.
*
* @param name
* @param parent
* @return
*/
public Repository createRepository(String name, Repository parent) {
Repository lg=new Repository(this);
lg.setName(name);
lg.setParent( parent );
repositories.put(name, lg);
if( listener != null ) {
listener.repositoryAdd(lg);
}
return lg;
}
public Enumeration getRepositoryNames() {
return repositories.keys();
}
/** Create a module using the NAME.loader property to construct the
* classpath.
*
* @param name
* @param parent
* @return
* @throws Exception
*/
private Repository initRepository(String name, Repository parent, ClassLoader pcl)
throws Exception
{
String value = getProperty(name + ".loader");
Repository lg=createRepository(name, parent );
if( pcl != null )
lg.setParentClassLoader( pcl );
if( DEBUG ) log( "Creating loading group " + name + " - " + value + " " + pcl);
if ((value == null) || (value.equals("")))
return lg;
ArrayList unpackedList = new ArrayList();
ArrayList packedList = new ArrayList();
ArrayList urlList = new ArrayList();
Vector repo=split( value );
Enumeration elems=repo.elements();
while (elems.hasMoreElements()) {
String repository = (String)elems.nextElement();
// Local repository
boolean packed = false;
if (repository.startsWith(CATALINA_HOME_TOKEN)) {
repository = getCatalinaHome()
+ repository.substring(CATALINA_HOME_TOKEN.length());
} else if (repository.startsWith(CATALINA_BASE_TOKEN)) {
repository = getCatalinaBase()
+ repository.substring(CATALINA_BASE_TOKEN.length());
}
// Check for a JAR URL repository
try {
urlList.add(new URL(repository));
continue;
} catch (MalformedURLException e) {
// Ignore
}
if (repository.endsWith("*.jar")) {
packed = true;
repository = repository.substring
(0, repository.length() - "*.jar".length());
}
if (packed) {
packedList.add(new File(repository));
} else {
unpackedList.add(new File(repository));
}
}
File[] unpacked = (File[]) unpackedList.toArray(new File[0]);
File[] packed = (File[]) packedList.toArray(new File[0]);
URL[] urls = (URL[]) urlList.toArray(new URL[0]);
// previously: ClassLoaderFactory.createClassLoader
initRepository(lg, unpacked, packed, urls, parent); //new ModuleGroup();
// TODO: JMX registration for the group loader
/*
// Register the server classloader
ObjectName objectName =
new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
mBeanServer.registerMBean(classLoader, objectName);
*/
return lg; // classLoader;
}
/** Small hack to allow a loader.properties file to be passed in CLI, to
* allow a more convenient method to start with different params from
* explorer/kde/etc.
*
* If the first arg ends with ".loader", it is used as loader.properties
* file and removed from args[].
*/
private void processCLI() {
if( args!=null && args.length > 0 &&
(args[0].toLowerCase().endsWith(".tomcat") ||
args[0].toLowerCase().endsWith(".loader") ||
args[0].toLowerCase().endsWith("loader.properties") )) {
String props=args[0];
String args2[]=new String[args.length-1];
System.arraycopy(args, 1, args2, 0, args2.length);
args=args2;
setPropertiesFile(props);
} else {
loadProperties();
}
}
/**
* Initialize:
* - detect the home/base directories
* - init the loaders / modules
* - instantiate the "startup" class(es)
*
*/
public void main()
throws Exception
{
processCLI();
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
init();
Thread.currentThread().setContextClassLoader(catalinaLoader);
securityPreload(catalinaLoader);
autostart();
}
private void autostart() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// Load our startup classes and call its process() method
/* Why multiple classes ?
* - maybe you want to start more "servers" ( tomcat ,something else )
* - easy hook for other load on startup modules ( like a jmx hook )
* - maybe split the loader-specific code from catalina
*/
String startupClasses=getProperty("loader.auto-startup",
"org.apache.catalina.startup.CatalinaModuleListener");
Vector v=split( startupClasses );
for( int i=0; i<v.size(); i++ ) {
String startupCls=(String)v.elementAt(i);
if (DEBUG)
log("Loading startup class " + startupCls);
Class startupClass =
catalinaLoader.loadClass(startupCls);
Object startupInstance = startupClass.newInstance();
if( startupInstance instanceof ModuleListener ) {
addModuleListener((ModuleListener)startupInstance);
// it can get args[] and properties from Loader
listener.setLoader(this);
// all arg processing moved there. Maybe we can make it consistent
// for all startup schemes
listener.start();
} else if ( startupInstance instanceof Runnable ) {
((Runnable)startupInstance).run();
} else {
Class paramTypes[] = new Class[0];
Object paramValues[] = new Object[0];
Method method =
startupInstance.getClass().getMethod("execute", paramTypes);
if( method==null )
method = startupInstance.getClass().getMethod("start", paramTypes);
if( method!=null )
method.invoke(startupInstance, paramValues);
}
}
}
/** Returns one of the repositories.
*
* Typically at startup we create at least: "common", "shared" and "server", with
* same meaning as in tomcat.
*
* @param name
* @return
*/
public Repository getRepository( String name ) {
return (Repository)repositories.get(name);
}
private static void securityPreload(ClassLoader loader)
throws Exception {
if( System.getSecurityManager() == null ){
return;
}
String value=getProperty("security.preload");
Vector repo=split( value );
Enumeration elems=repo.elements();
while (elems.hasMoreElements()) {
String classN = (String)elems.nextElement();
try {
loader.loadClass( classN);
} catch( Throwable t ) {
// ignore
}
}
}
// ----------------------------------------------------------- Main Program
/** Access to the command line arguments, when Loader is used to launc an app.
*/
public String[] getArgs() {
return args;
}
/**
* Main method.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
try {
if (daemon == null) {
daemon = new Loader();
daemon.args=args;
try {
daemon.main();
} catch (Throwable t) {
t.printStackTrace();
return;
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
/**
* Initialize the loader properties explicitely.
*
* TODO: add setPropertiesRes
*
* @param props
*/
public void setPropertiesFile(String props) {
propFile=props;
loadProperties();
}
/**
* Return specified property value.
*/
static String getProperty(String name) {
if( properties==null ) loadProperties();
return properties.getProperty(name);
}
/**
* Return specified property value.
*/
static String getProperty(String name, String defaultValue) {
if( properties==null ) loadProperties();
return properties.getProperty(name, defaultValue);
}
/**
* Load properties.
* Will try:
* - "catalina.config" system property ( a URL )
* - "catalina.base", "catalina.home", "user.dir" system properties +
* "/conf/" "../conf" "/" + "loader.properties" or "catalina.properties"
* - /org/apache/catalina/startup/catalina.properties
*
* Properties will be loaded as system properties.
*
* loader.properties was added to allow coexistence with bootstrap.jar ( the
* current scheme ), since the classpaths are slightly different.
*/
static void loadProperties() {
properties = new Properties();
InputStream is = null;
Throwable error = null;
// TODO: paste the code to do ${} substitution
// TODO: add the code to detect where tomcat-properties is loaded from
if( propFile != null ) {
try {
File properties = new File(propFile);
is = new FileInputStream(properties);
if( is!=null && DEBUG ) {
log("Loaded from loader.properties " + properties );
}
} catch( Throwable t) {
System.err.println("Can't find " + propFile);
return;
}
}
if( is == null ) {
try {
// "catalina.config" system property
String configUrl = System.getProperty("catalina.config");
if (configUrl != null) {
is = (new URL(configUrl)).openStream();
if( is!=null && DEBUG ) {
log("Loaded from catalina.config " + configUrl );
}
}
} catch (Throwable t) {
// Ignore
}
}
if( is == null ) {
try {
// "loader.config" system property
String configUrl = System.getProperty("loader.config");
if (configUrl != null) {
is = (new URL(configUrl)).openStream();
if( is!=null && DEBUG ) {
log("Loaded from catalina.config " + configUrl );
}
}
} catch (Throwable t) {
// Ignore
}
}
if (is == null) {
try {
setCatalinaBase(); // use system properties, then user.dir
File home = new File(getCatalinaBase());
File conf = new File(home, "conf");
// use conf if exists, or the base directory otherwise
if( ! conf.exists() ) conf = new File(home, "../conf");
if( ! conf.exists() ) conf = home;
File propertiesF=null;
if( conf.exists() )
propertiesF= new File(conf, "loader.properties");
if( ! propertiesF.exists() ) {
propertiesF= new File( home, "loader.properties");
}
if( propertiesF.exists() )
is = new FileInputStream(propertiesF);
if( is!=null && DEBUG ) {
log("Loaded from loader.properties " + properties );
}
} catch (Throwable t) {
// Ignore
}
}
if (is == null) {
try {
File home = new File(getCatalinaBase());
File conf = new File(home, "conf");
File properties = new File(conf, "catalina.properties");
is = new FileInputStream(properties);
if( is!=null && DEBUG ) {
log("Loaded from catalina.properties " + properties );
}
} catch (Throwable t) {
// Ignore
}
}
if (is == null) {
try {
is = Loader.class.getResourceAsStream
("/org/apache/catalina/startup/catalina.properties");
if( is!=null && DEBUG ) {
log("Loaded from o/a/c/startup/catalina.properties " );
}
} catch (Throwable t) {
// Ignore
}
}
if (is == null) {
try {
is = Loader.class.getResourceAsStream
("loader.properties");
if( is!=null && DEBUG ) {
log("Loaded from res loader.properties " );
}
} catch (Throwable t) {
// Ignore
}
}
if (is != null) {
try {
properties.load(is);
is.close();
} catch (Throwable t) {
error = t;
}
}
// if ((is == null) || (error != null)) {
// // Do something
// log("Error: no properties found !!!");
// }
// Register the _unused_ properties as system properties
if( properties != null ) {
Enumeration enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String name = (String) enumeration.nextElement();
String value = properties.getProperty(name);
if( "security.preload".equals( name )) continue;
if( "package.access".equals( name )) continue;
if( "package.definition".equals( name )) continue;
if( name.endsWith(".loader")) continue;
if( name.startsWith("loader.")) continue;
if (value != null) {
System.setProperty(name, value);
}
}
}
}
static void setCatalinaHome(String s) {
System.setProperty( "catalina.home", s );
}
static void setCatalinaBase(String s) {
System.setProperty( "catalina.base", s );
}
/**
* Get the value of the catalina.home environment variable.
*
* @deprecated
*/
static String getCatalinaHome() {
if( properties==null ) loadProperties();
return System.getProperty("catalina.home",
System.getProperty("user.dir"));
}
/**
* Get the value of the catalina.base environment variable.
*
* @deprecated
*/
static String getCatalinaBase() {
if( properties==null ) loadProperties();
return System.getProperty("catalina.base", getCatalinaHome());
}
/**
* Set the <code>catalina.base</code> System property to the current
* working directory if it has not been set.
*/
static void setCatalinaBase() {
if( properties==null ) loadProperties();
if (System.getProperty("catalina.base") != null)
return;
if (System.getProperty("catalina.home") != null)
System.setProperty("catalina.base",
System.getProperty("catalina.home"));
else
System.setProperty("catalina.base",
System.getProperty("user.dir"));
}
/**
* Set the <code>catalina.home</code> System property to the current
* working directory if it has not been set.
*/
static void setCatalinaHome() {
if (System.getProperty("catalina.home") != null)
return;
File bootstrapJar =
new File(System.getProperty("user.dir"), "bootstrap.jar");
File tloaderJar =
new File(System.getProperty("user.dir"), "tomcat-loader.jar");
if (bootstrapJar.exists() || tloaderJar.exists()) {
try {
System.setProperty
("catalina.home",
(new File(System.getProperty("user.dir"), ".."))
.getCanonicalPath());
} catch (Exception e) {
// Ignore
System.setProperty("catalina.home",
System.getProperty("user.dir"));
}
} else {
System.setProperty("catalina.home",
System.getProperty("user.dir"));
}
}
/**
* Get the module from the classloader. Works only for classloaders created by
* this package - or extending ModuleClassLoader.
*
* This shold be the only public method that allows this - Loader acts as a
* guard, only if you have the loader instance you can access the internals.
*
*
* @param cl
* @return
*/
public Module getModule(ClassLoader cl ) {
if( cl instanceof ModuleClassLoader ) {
return ((ModuleClassLoader)cl).getModule();
}
return null;
}
/**
* Create and return a new class loader, based on the configuration
* defaults and the specified directory paths:
*
* @param unpacked Array of pathnames to unpacked directories that should
* be added to the repositories of the class loader, or <code>null</code>
* for no unpacked directories to be considered
* @param packed Array of pathnames to directories containing JAR files
* that should be added to the repositories of the class loader,
* or <code>null</code> for no directories of JAR files to be considered
* @param urls Array of URLs to remote repositories, designing either JAR
* resources or uncompressed directories that should be added to
* the repositories of the class loader, or <code>null</code> for no
* directories of JAR files to be considered
* @param parent Parent class loader for the new class loader, or
* <code>null</code> for the system class loader.
*
* @exception Exception if an error occurs constructing the class loader
*/
private void initRepository(Repository lg, File unpacked[],
File packed[], URL urls[], Repository parent)
throws Exception
{
StringBuffer sb=new StringBuffer();
// Construct the "class path" for this class loader
ArrayList list = new ArrayList();
// Add unpacked directories
if (unpacked != null) {
for (int i = 0; i < unpacked.length; i++) {
File file = unpacked[i];
if (!file.exists() || !file.canRead()) {
if (DEBUG)
log(" Not found: "+ file.getAbsolutePath());
continue;
}
// String cPath=file.getCanonicalPath();
// URL url=null;
//
// if( cPath.toLowerCase().endsWith(".jar") ||
// cPath.toLowerCase().endsWith(".zip") ) {
// url = new URL("file", null, cPath);
// } else {
// url = new URL("file", null, cPath + File.separator);
// }
URL url=file.toURL();
if (DEBUG)
sb.append(" : "+ url);
if( ! FLAT ) {
addLoader(lg, parent, new URL[] { url });
} else {
list.add(url);
}
}
}
// Add packed directory JAR files
if (packed != null) {
for (int i = 0; i < packed.length; i++) {
File directory = packed[i];
if (!directory.isDirectory() || !directory.exists() ||
!directory.canRead()) {
if (DEBUG)
log(" Not found: "+ directory.getAbsolutePath());
continue;
}
String filenames[] = directory.list();
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase();
if (!filename.endsWith(".jar"))
continue;
File file = new File(directory, filenames[j]);
// if (DEBUG)
// sb.append(" [pak]="+ file.getCanonicalPath());
// URL url = new URL("file", null,
// file.getCanonicalPath());
URL url=file.toURL();
if (DEBUG)
sb.append(" pk="+ url);
if( ! FLAT ) {
addLoader(lg, parent, new URL[] { url });
} else {
list.add(url);
}
}
}
}
// Add URLs
if (urls != null) {
for (int i = 0; i < urls.length; i++) {
if( ! FLAT ) {
addLoader(lg, parent, new URL[] { urls[i] });
} else {
list.add(urls[i]);
}
if (DEBUG)
sb.append(" "+ urls[i]);
}
}
// Construct the class loader itself
// TODO: experiment with loading each jar in a separate loader.
if (DEBUG)
log("Creating new class loader " + lg.getName() + " " + sb.toString());
URL[] array = (URL[]) list.toArray(new URL[list.size()]);
if( array.length > 0 ) {
addLoader(lg, parent, array);
}
}
/**
* @param lg
* @param parent
* @param list
*/
private void addLoader(Repository lg, Repository parent, URL array[])
throws Exception
{
Module module=new Module();
module.setParent( parent );
module.setClasspath( array );
lg.addModule(module);
}
private static Vector split( String value ) {
Vector result=new Vector();
StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken();
repository.trim();
if( ! "".equals(repository) )
result.addElement(repository);
}
return result;
}
void notifyModuleStart(Module module) {
if(listener!=null) listener.moduleStart(module);
}
void notifyModuleStop(Module module) {
if( listener!=null ) listener.moduleStop(module);
}
/** Add a module listener.
*
* To keep the dependencies minimal, the loader package only implements the
* basic class loading mechanism - but any advanced feature ( management,
* policy, etc ) should be implemented by a module.
*
* @param listener
*/
public void addModuleListener(ModuleListener listener) {
this.listener=listener;
}
private static void log(String s) {
System.err.println("Main: " + s);
}
private static void log( String msg, Throwable t ) {
System.err.println("Main: " + msg);
t.printStackTrace();
}
}