595 lines
19 KiB
Java
595 lines
19 KiB
Java
/*
|
|
* 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.Globals;
|
|
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
|
|
* @author Remy Maucherat
|
|
*/
|
|
public final class Bootstrap {
|
|
|
|
private static final Log log = LogFactory.getLog(Bootstrap.class);
|
|
|
|
/**
|
|
* 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(Globals.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(
|
|
Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
|
|
|
|
// Then base
|
|
String base = System.getProperty(Globals.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(
|
|
Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
|
|
}
|
|
|
|
// -------------------------------------------------------------- Variables
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @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 (Globals.CATALINA_HOME_PROP.equals(propName)) {
|
|
replacement = getCatalinaHome();
|
|
} else if (Globals.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
|
|
*/
|
|
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.
|
|
*/
|
|
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
|
|
*/
|
|
private Object getServer() throws Exception {
|
|
|
|
String methodName = "getServer";
|
|
Method method = catalinaDaemon.getClass().getMethod(methodName);
|
|
return method.invoke(catalinaDaemon);
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------- Main Program
|
|
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
public void stopServer() throws Exception {
|
|
|
|
Method method =
|
|
catalinaDaemon.getClass().getMethod("stopServer", (Class []) null);
|
|
method.invoke(catalinaDaemon, (Object []) null);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
* @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.
|
|
*/
|
|
public void destroy() {
|
|
|
|
// FIXME
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* 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).
|
|
* @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 {@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).
|
|
* @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 {@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
|
|
private static void handleThrowable(Throwable t) {
|
|
if (t instanceof ThreadDeath) {
|
|
throw (ThreadDeath) t;
|
|
}
|
|
if (t instanceof VirtualMachineError) {
|
|
throw (VirtualMachineError) t;
|
|
}
|
|
// All other instances of Throwable will be silently swallowed
|
|
}
|
|
|
|
|
|
// 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 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[result.size()]);
|
|
}
|
|
}
|