/*
* 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.catalina.startup;
import java.io.File;
import java.io.IOException;
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.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.catalina.security.SecurityClassLoad;
import org.apache.catalina.startup.ClassLoaderFactory.Repository;
import org.apache.catalina.startup.ClassLoaderFactory.RepositoryType;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
Bootstrap loader for Catalina. 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.
Author: Craig R. McClanahan, Remy Maucherat
/**
* Bootstrap loader for Catalina. 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.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public final class Bootstrap {
private static final Log log = LogFactory.getLog(Bootstrap.class);
Daemon object used by main.
/**
* Daemon object used by main.
*/
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;
private static final File catalinaBaseFile;
private static final File catalinaHomeFile;
private static final Pattern PATH_PATTERN = Pattern.compile("(\"[^\"]*\")|(([^,])*)");
static {
// Will always be non-null
String userDir = System.getProperty("user.dir");
// Home first
String home = System.getProperty(Constants.CATALINA_HOME_PROP);
File homeFile = null;
if (home != null) {
File f = new File(home);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
if (homeFile == null) {
// First fall-back. See if current directory is a bin directory
// in a normal Tomcat install
File bootstrapJar = new File(userDir, "bootstrap.jar");
if (bootstrapJar.exists()) {
File f = new File(userDir, "..");
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
}
if (homeFile == null) {
// Second fall-back. Use current directory
File f = new File(userDir);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
catalinaHomeFile = homeFile;
System.setProperty(
Constants.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
// Then base
String base = System.getProperty(Constants.CATALINA_BASE_PROP);
if (base == null) {
catalinaBaseFile = catalinaHomeFile;
} else {
File baseFile = new File(base);
try {
baseFile = baseFile.getCanonicalFile();
} catch (IOException ioe) {
baseFile = baseFile.getAbsoluteFile();
}
catalinaBaseFile = baseFile;
}
System.setProperty(
Constants.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
// -------------------------------------------------------------- Variables
Daemon reference.
/**
* Daemon reference.
*/
private Object catalinaDaemon = null;
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
// -------------------------------------------------------- Private Methods
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
System property replacement in the given string.
Params: - str – The original string
Returns: the modified string
/**
* System property replacement in the given string.
*
* @param str The original string
* @return the modified string
*/
protected String replace(String str) {
// Implementation is copied from ClassLoaderLogManager.replace(),
// but added special processing for catalina.home and catalina.base.
String result = str;
int pos_start = str.indexOf("${");
if (pos_start >= 0) {
StringBuilder builder = new StringBuilder();
int pos_end = -1;
while (pos_start >= 0) {
builder.append(str, pos_end + 1, pos_start);
pos_end = str.indexOf('}', pos_start + 2);
if (pos_end < 0) {
pos_end = pos_start - 1;
break;
}
String propName = str.substring(pos_start + 2, pos_end);
String replacement;
if (propName.length() == 0) {
replacement = null;
} else if (Constants.CATALINA_HOME_PROP.equals(propName)) {
replacement = getCatalinaHome();
} else if (Constants.CATALINA_BASE_PROP.equals(propName)) {
replacement = getCatalinaBase();
} else {
replacement = System.getProperty(propName);
}
if (replacement != null) {
builder.append(replacement);
} else {
builder.append(str, pos_start, pos_end + 1);
}
pos_start = str.indexOf("${", pos_end + 1);
}
builder.append(str, pos_end + 1, str.length());
result = builder.toString();
}
return result;
}
Initialize daemon.
Throws: - Exception – Fatal initialization error
/**
* Initialize daemon.
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
Load daemon.
/**
* Load daemon.
*/
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}
getServer() for configtest
/**
* getServer() for configtest
*/
private Object getServer() throws Exception {
String methodName = "getServer";
Method method = catalinaDaemon.getClass().getMethod(methodName);
return method.invoke(catalinaDaemon);
}
// ----------------------------------------------------------- Main Program
Load the Catalina daemon.
Params: - arguments – Initialization arguments
Throws: - Exception – Fatal initialization error
/**
* Load the Catalina daemon.
* @param arguments Initialization arguments
* @throws Exception Fatal initialization error
*/
public void init(String[] arguments) throws Exception {
init();
load(arguments);
}
Start the Catalina daemon.
Throws: - Exception – Fatal start error
/**
* Start the Catalina daemon.
* @throws Exception Fatal start error
*/
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
Stop the Catalina Daemon.
Throws: - Exception – Fatal stop error
/**
* Stop the Catalina Daemon.
* @throws Exception Fatal stop error
*/
public void stop() throws Exception {
Method method = catalinaDaemon.getClass().getMethod("stop", (Class []) null);
method.invoke(catalinaDaemon, (Object []) null);
}
Stop the standalone server.
Throws: - Exception – Fatal stop error
/**
* Stop the standalone server.
* @throws Exception Fatal stop error
*/
public void stopServer() throws Exception {
Method method =
catalinaDaemon.getClass().getMethod("stopServer", (Class []) null);
method.invoke(catalinaDaemon, (Object []) null);
}
Stop the standalone server.
Params: - arguments – Command line arguments
Throws: - Exception – Fatal stop error
/**
* Stop the standalone server.
* @param arguments Command line arguments
* @throws Exception Fatal stop error
*/
public void stopServer(String[] arguments) throws Exception {
Object param[];
Class<?> paramTypes[];
if (arguments == null || arguments.length == 0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
method.invoke(catalinaDaemon, param);
}
Set flag.
Params: - await –
true
if the daemon should block
Throws: - Exception – Reflection error
/**
* Set flag.
* @param await <code>true</code> if the daemon should block
* @throws Exception Reflection error
*/
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
public boolean getAwait() throws Exception {
Class<?> paramTypes[] = new Class[0];
Object paramValues[] = new Object[0];
Method method =
catalinaDaemon.getClass().getMethod("getAwait", paramTypes);
Boolean b=(Boolean)method.invoke(catalinaDaemon, paramValues);
return b.booleanValue();
}
Destroy the Catalina Daemon.
/**
* Destroy the Catalina Daemon.
*/
public void destroy() {
// FIXME
}
Main method and entry point when starting Tomcat via the provided
scripts.
Params: - args – Command line arguments to be processed
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
Obtain the name of configured home (binary) directory. Note that home and
base may be the same (and are by default).
Returns: the catalina home
/**
* Obtain the name of configured home (binary) directory. Note that home and
* base may be the same (and are by default).
* @return the catalina home
*/
public static String getCatalinaHome() {
return catalinaHomeFile.getPath();
}
Obtain the name of the configured base (instance) directory. Note that home and base may be the same (and are by default). If this is not set the value returned by getCatalinaHome()
will be used. Returns: the catalina base
/**
* Obtain the name of the configured base (instance) directory. Note that
* home and base may be the same (and are by default). If this is not set
* the value returned by {@link #getCatalinaHome()} will be used.
* @return the catalina base
*/
public static String getCatalinaBase() {
return catalinaBaseFile.getPath();
}
Obtain the configured home (binary) directory. Note that home and
base may be the same (and are by default).
Returns: the catalina home as a file
/**
* Obtain the configured home (binary) directory. Note that home and
* base may be the same (and are by default).
* @return the catalina home as a file
*/
public static File getCatalinaHomeFile() {
return catalinaHomeFile;
}
Obtain the configured base (instance) directory. Note that home and base may be the same (and are by default). If this is not set the value returned by getCatalinaHomeFile()
will be used. Returns: the catalina base as a file
/**
* Obtain the configured base (instance) directory. Note that
* home and base may be the same (and are by default). If this is not set
* the value returned by {@link #getCatalinaHomeFile()} will be used.
* @return the catalina base as a file
*/
public static File getCatalinaBaseFile() {
return catalinaBaseFile;
}
// Copied from ExceptionUtils since that class is not visible during start
static void handleThrowable(Throwable t) {
if (t instanceof ThreadDeath) {
throw (ThreadDeath) t;
}
if (t instanceof StackOverflowError) {
// Swallow silently - it should be recoverable
return;
}
if (t instanceof VirtualMachineError) {
throw (VirtualMachineError) t;
}
// All other instances of Throwable will be silently swallowed
}
// Copied from ExceptionUtils so that there is no dependency on utils
static Throwable unwrapInvocationTargetException(Throwable t) {
if (t instanceof InvocationTargetException && t.getCause() != null) {
return t.getCause();
}
return t;
}
// Protected for unit testing
protected static String[] getPaths(String value) {
List<String> result = new ArrayList<>();
Matcher matcher = PATH_PATTERN.matcher(value);
while (matcher.find()) {
String path = value.substring(matcher.start(), matcher.end());
path = path.trim();
if (path.length() == 0) {
continue;
}
char first = path.charAt(0);
char last = path.charAt(path.length() - 1);
if (first == '"' && last == '"' && path.length() > 1) {
path = path.substring(1, path.length() - 1);
path = path.trim();
if (path.length() == 0) {
continue;
}
} else if (path.contains("\"")) {
// Unbalanced quotes
// Too early to use standard i18n support. The class path hasn't
// been configured.
throw new IllegalArgumentException(
"The double quote [\"] character can only be used to quote paths. It must " +
"not appear in a path. This loader path is not valid: [" + value + "]");
} else {
// Not quoted - NO-OP
}
result.add(path);
}
return result.toArray(new String[0]);
}
}