// ========================================================================
// Copyright (c) 2002 Mort Bay Consulting (Australia) Pty. Ltd.
// $Id$
// ========================================================================
//Modified for eXist-db
/**
* This is an adopted version of the corresponding classes shipped
* with Jetty.
*/
package org.exist.start;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Jan Hlavaty (hlavac@code.cz)
* @author Wolfgang Meier (meier@ifs.tu-darmstadt.de)
* @version $Revision$
* <p/>
* TODO:
* - finish possible jetty.home locations
* - better handling of errors (i.e. when jetty.home cannot be autodetected...)
* - include entries from lib _when needed_
*/
public class Main {
private final static String START_CONFIG = "start.config";
public static final String STANDARD_ENABLED_JETTY_CONFIGS = "standard.enabled-jetty-configs";
public static final String STANDALONE_ENABLED_JETTY_CONFIGS = "standalone.enabled-jetty-configs";
private String _classname = null;
private String _mode = "jetty";
private static Main exist;
private boolean _debug = Boolean.getBoolean("exist.start.debug");
// Stores the path to the "start.config" file that's used to configure
// the runtime classpath.
private String startConfigFileName = "";
// Used to find latest version of jar files that should be added to the
// classpath.
private final LatestFileResolver jarFileResolver = new LatestFileResolver();
public static void main(final String[] args) {
try {
getMain().run(args);
} catch (final Exception e) {
e.printStackTrace();
}
}
/**
* Singleton Factory Method
*/
public static Main getMain() {
if (exist == null) {
exist = new Main();
}
return exist;
}
public String getMode() {
return this._mode;
}
private Main() {
}
public Main(final String mode) {
this._mode = mode;
}
static Path getDirectory(final String name) {
try {
if (name != null) {
final Path dir = Paths.get(name).normalize().toAbsolutePath();
if (Files.isDirectory(dir)) {
return dir;
}
}
} catch (final InvalidPathException e) {
// NOP
}
return null;
}
boolean isAvailable(final String classname, final Classpath classpath) {
try {
Class.forName(classname);
return true;
} catch (final ClassNotFoundException e) {
//ignore
}
final ClassLoader loader = classpath.getClassLoader(null);
try {
loader.loadClass(classname);
return true;
} catch (final ClassNotFoundException e) {
//ignore
}
return false;
}
public static void invokeMain(final ClassLoader classloader, final String classname, final String[] args)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException, ClassNotFoundException {
final Class<?> invoked_class = classloader.loadClass(classname);
final Class<?>[] method_param_types = new Class[1];
method_param_types[0] = args.getClass();
final Method main = invoked_class.getDeclaredMethod("main", method_param_types);
final Object[] method_params = new Object[1];
method_params[0] = args;
main.invoke(null, method_params);
}
void configureClasspath(final Path home, final Classpath classpath, final InputStream config, final String[] args, final String mode) {
// Any files referenced in start.config that don't exist or cannot be resolved
// are placed in this list.
final List<Path> invalidJars = new ArrayList<>();
try(final BufferedReader cfg = new BufferedReader(new InputStreamReader(config, StandardCharsets.UTF_8))) {
final Version java_version = new Version(System.getProperty("java.version"));
final Version ver = new Version();
// JAR's already processed
final Set<Path> done = new HashSet<>();
String line;
while ((line = cfg.readLine()) != null) {
try {
if ((line.length() > 0) && (!line.startsWith("#"))) {
if (_debug) {
System.err.println(">" + line);
}
final StringTokenizer st = new StringTokenizer(line);
final String subject = st.nextToken();
boolean include_subject = true;
String condition = null;
while (include_subject && st.hasMoreTokens()) {
condition = st.nextToken();
if ("never".equals(condition)) {
include_subject = false;
} else if ("always".equals(condition)) {
// ignore
} else if ("available".equals(condition)) {
final String class_to_check = st.nextToken();
include_subject &= isAvailable(class_to_check, classpath);
} else if ("!available".equals(condition)) {
final String class_to_check = st.nextToken();
include_subject &= !isAvailable(class_to_check, classpath);
} else if ("java".equals(condition)) {
final String operator = st.nextToken();
final String version = st.nextToken();
ver.parse(version);
include_subject &= ("<".equals(operator) && java_version.compare(ver) < 0)
|| (">".equals(operator) && java_version.compare(ver) > 0)
|| ("<=".equals(operator) && java_version.compare(ver) <= 0)
|| ("=<".equals(operator) && java_version.compare(ver) <= 0)
|| ("=>".equals(operator) && java_version.compare(ver) >= 0)
|| (">=".equals(operator) && java_version.compare(ver) >= 0)
|| ("==".equals(operator) && java_version.compare(ver) == 0)
|| ("!=".equals(operator) && java_version.compare(ver) != 0);
} else if ("nargs".equals(condition)) {
final String operator = st.nextToken();
final int number = Integer.parseInt(st.nextToken());
include_subject &= ("<".equals(operator) && args.length < number)
|| (">".equals(operator) && args.length > number)
|| ("<=".equals(operator) && args.length <= number)
|| ("=<".equals(operator) && args.length <= number)
|| ("=>".equals(operator) && args.length >= number)
|| (">=".equals(operator) && args.length >= number)
|| ("==".equals(operator) && args.length == number)
|| ("!=".equals(operator) && args.length != number);
} else if ("mode".equals(condition)) {
final String operator = st.nextToken();
final String m = st.nextToken();
include_subject &= ("==".equals(operator) && mode.equals(m))
|| ("!=".equals(operator) && (!mode.equals(m)));
} else {
System.err.println("ERROR: Unknown condition: " + condition);
}
}
final String file =
subject.startsWith("/")
? subject.replace('/', File.separatorChar)
: home.toAbsolutePath().toString() + File.separatorChar + subject.replace('/', File.separatorChar);
if (_debug) {
System.err.println("subject=" + subject + " file=" + file
+ " condition=" + condition + " include_subject=" + include_subject);
}
// ok, should we include?
if (subject.endsWith("/*")) {
// directory of JAR files
final Path extdir = Paths.get(file.substring(0, file.length() - 1));
final List<Path> jars = list(extdir, p -> fileName(p).toLowerCase().endsWith(".jar") || fileName(p).toLowerCase().endsWith(".zip"));
if (jars != null) {
for (final Path jarFile : jars) {
final Path canonicalPath = jarFile.toAbsolutePath();
if (!done.contains(canonicalPath)) {
if (include_subject) {
done.add(canonicalPath);
if (classpath.addComponent(canonicalPath) && _debug) {
System.err.println("Adding JAR from directory: " + canonicalPath);
}
}
}
}
}
} else if (subject.endsWith("/")) {
// class directory
final Path p = Paths.get(file).toAbsolutePath();
if (!done.contains(p)) {
done.add(p);
if (include_subject) {
if (classpath.addComponent(p) && _debug) {
System.err.println("Adding directory: " + p);
}
}
}
} else if (subject.toLowerCase().endsWith(".class")) {
// Class
_classname = subject.substring(0, subject.length() - 6);
} else {
// single JAR file
final String resolvedFile = jarFileResolver.getResolvedFileName(file);
final Path f = Paths.get(resolvedFile);
if (include_subject) {
if (!Files.exists(f)) {
invalidJars.add(f.toAbsolutePath());
}
}
final Path d = f.toAbsolutePath();
if (!done.contains(d)) {
if (include_subject) {
done.add(d);
if (classpath.addComponent(d) && _debug) {
System.err.println("Adding single JAR: " + d);
}
}
}
}
}
} catch (final Exception e) {
if (_debug) {
System.err.println(line);
e.printStackTrace();
}
}
}
} catch (final Exception e) {
e.printStackTrace();
}
// Print message if any files from start.config were added
// to the classpath but they could not be found.
// if (invalidJars.size() > 0) {
// final StringBuilder nonexistentJars = new StringBuilder();
// for (final String invalidJar : invalidJars) {
// nonexistentJars.append(" " + invalidJar + "\n");
// }
/*
System.err.println(
"\nWARN: The following JAR file entries from '"
+ startConfigFileName + "' aren't available (this may NOT be a "
+ "problem):\n"
+ nonexistentJars
);
*/
// }
}
public void run(String[] args) {
if (args.length > 0) {
if ("client".equals(args[0])) {
//_classname = "org.exist.client.InteractiveClient";
_classname = "org.exist.client.InteractiveClient";
_mode = "client";
} else if ("backup".equals(args[0])) {
_classname = "org.exist.backup.Main";
_mode = "backup";
} else if ("jetty".equals(args[0]) || "standalone".equals(args[0])) {
//_classname = "org.mortbay.jetty.Server";
_classname = "org.exist.jetty.JettyStart";
_mode = args[0];
} else if ("launch".equals(args[0])) {
_classname = "org.exist.launcher.LauncherWrapper";
_mode = "jetty";
} else if ("shutdown".equals(args[0])) {
_classname = "org.exist.jetty.ServerShutdown";
_mode = "other";
} else {
_classname = args[0];
_mode = "other";
}
String[] nargs = new String[args.length - 1];
if (args.length > 1) {
System.arraycopy(args, 1, nargs, 0, args.length - 1);
}
args = nargs;
} else {
_classname = "org.exist.launcher.LauncherWrapper";
_mode = "other";
}
if (_debug) {
System.err.println("mode = " + _mode);
}
final Path _home_dir = detectHome();
//TODO: more attempts here...
if (_home_dir != null) {
// if we managed to detect exist.home, store it in system property
if (_debug) {
System.err.println("EXIST_HOME=" + System.getProperty("exist.home"));
}
// DWES #### can this be removed?
System.setProperty("exist.home", _home_dir.toString());
System.setProperty("user.dir", _home_dir.toString());
// try to find Jetty
if ("jetty".equals(_mode) || "standalone".equals(_mode)) {
if (System.getProperty("jetty.home") == null) {
final Path _tools_dir = _home_dir.resolve("tools");
if (!Files.exists(_tools_dir)) {
System.err.println("ERROR: tools directory not found in " + _home_dir.toAbsolutePath());
return;
}
try {
final List<Path> _dirs = list(_tools_dir, p -> Files.isDirectory(p) && fileName(p).startsWith("jetty"));
if(_dirs.size() > 0) {
System.setProperty("jetty.home", _dirs.get(0).toAbsolutePath().toString());
} else {
System.err.println("ERROR: Jetty could not be found in " + _tools_dir.toAbsolutePath());
return;
}
} catch(final IOException e) {
System.err.println("ERROR: Jetty could not be found in " + _tools_dir.toAbsolutePath());
e.printStackTrace();
return;
}
}
final String config;
if ("jetty".equals(_mode)) {
config = STANDARD_ENABLED_JETTY_CONFIGS;
} else {
config = STANDALONE_ENABLED_JETTY_CONFIGS;
}
args = new String[]{System.getProperty("jetty.home") + File.separatorChar + "etc"
+ File.separatorChar + config};
}
// find log4j2.xml
final Path log4j = Optional.ofNullable(System.getProperty("log4j.configurationFile"))
.map(Paths::get)
.orElseGet(() -> _home_dir.resolve("log4j2.xml"));
if (Files.isReadable(log4j)) {
System.setProperty("log4j.configurationFile", log4j.toUri().toASCIIString());
}
//redirect JUL to log4j2 unless otherwise specified
System.setProperty("java.util.logging.manager", Optional.ofNullable(System.getProperty("java.util.logging.manager")).orElse("org.apache.logging.log4j.jul.LogManager"));
// clean up tempdir for Jetty...
try {
final Path tmpdir = Paths.get(System.getProperty("java.io.tmpdir")).toAbsolutePath();
if (Files.isDirectory(tmpdir)) {
System.setProperty("java.io.tmpdir", tmpdir.toString());
}
} catch (final InvalidPathException e) {
// ignore
}
final Classpath _classpath = constructClasspath(_home_dir, args);
final EXistClassLoader cl = _classpath.getClassLoader(null);
Thread.currentThread().setContextClassLoader(cl);
if (_debug) {
System.err.println("TEMPDIR=" + System.getProperty("java.io.tmpdir"));
}
// Invoke org.mortbay.jetty.Server.main(args) using new classloader.
try {
invokeMain(cl, _classname, args);
} catch (final Exception e) {
e.printStackTrace();
}
} else {
// if not, warn user
System.err.println("ERROR: exist.home could not be autodetected, bailing out.");
System.err.flush();
}
}
/**
*/
public Path detectHome() {
//--------------------
// detect exist.home:
//--------------------
// DWES #### use Configuration.getExistHome() ?
Path _home_dir = getDirectory(System.getProperty("exist.home"));
if (_home_dir == null) {
// if eXist is deployed as web application, try to find WEB-INF first
final Path webinf = Paths.get("WEB-INF");
if (_debug) {
System.err.println("trying " + webinf.toAbsolutePath());
}
if (Files.exists(webinf)) {
final Path jar = webinf.resolve("lib").resolve("exist.jar");
if (Files.exists(jar)) {
try {
_home_dir = webinf.toAbsolutePath();
} catch (final InvalidPathException e) {
// ignore
}
}
}
}
if (_home_dir == null) {
// failed: try exist.jar in current directory
final Path jar = Paths.get("exist.jar");
if (_debug) {
System.err.println("trying " + jar.toAbsolutePath());
}
if (Files.isReadable(jar)) {
try {
_home_dir = Paths.get(".").normalize().toAbsolutePath();
} catch (final InvalidPathException e) {
// ignore
}
}
}
if (_home_dir == null) {
// failed: try ../exist.jar
final Path jar = Paths.get("..").resolve("exist.jar").normalize();
if (_debug) {
System.err.println("trying " + jar.toAbsolutePath());
}
if (Files.exists(jar)) {
try {
_home_dir = jar.getParent().toAbsolutePath();
} catch (final InvalidPathException e) {
// ignore
}
}
}
// searching exist.jar failed, try conf.xml to have the configuration at least
if (_home_dir == null) {
// try conf.xml in current dir
final Path jar = Paths.get("conf.xml");
if (_debug) {
System.err.println("trying " + jar.toAbsolutePath());
}
if (Files.isReadable(jar)) {
try {
_home_dir = Paths.get(".").toAbsolutePath();
} catch (final InvalidPathException e) {
// ignore
}
}
}
if (_home_dir == null) {
// try ../conf.xml
final Path jar = Paths.get("..").resolve("conf.xml").normalize();
if (_debug) {
System.err.println("trying " + jar.toAbsolutePath());
}
if (Files.exists(jar)) {
try {
_home_dir = jar.getParent().toAbsolutePath();
} catch (final InvalidPathException e) {
// ignore
}
}
}
return _home_dir;
}
/**
* @param args
*/
public Classpath constructClasspath(final Path homeDir, final String[] args) {
// set up classpath:
final Classpath _classpath = new Classpath();
// prefill existing paths in classpath_dirs...
if (_debug) {
System.out.println("existing classpath = " + System.getProperty("java.class.path"));
}
_classpath.addClasspath(System.getProperty("java.class.path"));
// add JARs from ext and lib
// be smart about it
try {
final BiConsumer<String, InputStream> configureClasspath = (path, is) -> {
if (_debug) {
System.err.println("Configuring classpath from: " + path);
}
configureClasspath(homeDir, _classpath, is, args, _mode);
};
// start.config can be found in one of two locations...
final Path configFilePath1 = homeDir.resolve(START_CONFIG);
if(Files.exists(configFilePath1)) {
try(final InputStream cpcfg = Files.newInputStream(configFilePath1)) {
final String cfgPath = configFilePath1.toAbsolutePath().toString();
configureClasspath.accept(cfgPath, cpcfg);
this.startConfigFileName = cfgPath;
}
} else {
if (_debug) {
System.err.println("Configuring classpath from default resource");
}
final String configFilePath2 = "org/exist/start/" + START_CONFIG;
final URL configFilePath2Url = getClass().getClassLoader().getResource(configFilePath2);
if(configFilePath2Url != null) {
try(final InputStream cpcfg = getClass().getClassLoader().getResourceAsStream(configFilePath2)) {
configureClasspath.accept(configFilePath2, cpcfg);
this.startConfigFileName = configFilePath2;
}
} else {
throw new RuntimeException(START_CONFIG + " not found at " + configFilePath1 + " or " + configFilePath2 + ", Bailing out.");
}
}
} catch (final IOException e) {
e.printStackTrace();
}
// try to find javac and add it in classpaths
final String java_home = System.getProperty("java.home");
if (java_home != null) {
Path jdk_home = null;
try {
jdk_home = Paths.get(java_home).getParent().toAbsolutePath();
} catch (final InvalidPathException e) {
// ignore
}
if (jdk_home != null) {
Path tools_jar_file = null;
try {
tools_jar_file = jdk_home.resolve("lib").resolve("tools.jar").toAbsolutePath();
} catch (final InvalidPathException e) {
// ignore
}
if ((tools_jar_file != null) && Files.isRegularFile(tools_jar_file)) {
// OK, found tools.jar in java.home/../lib
// add it in
_classpath.addComponent(tools_jar_file);
if (_debug) {
System.err.println("JAVAC = " + tools_jar_file);
}
}
}
}
// okay, classpath complete.
System.setProperty("java.class.path", _classpath.toString());
if (_debug) {
System.err.println("CLASSPATH=" + _classpath.toString());
}
return _classpath;
}
public void shutdown() {
// only used in test suite
try {
final Class brokerPool = Class.forName("org.exist.storage.BrokerPools");
final Method stopAll = brokerPool.getDeclaredMethod("stopAll", boolean.class);
stopAll.setAccessible(true);
stopAll.invoke(null, false);
} catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* Copied from {@link org.exist.util.FileUtils#list(Path, Predicate)}
* as org.exist.start is compiled into a separate Jar and doesn't have
* the rest of eXist available on the classpath
*/
static List<Path> list(final Path directory, final Predicate<Path> filter) throws IOException {
try(final Stream<Path> entries = Files.list(directory).filter(filter)) {
return entries.collect(Collectors.toList());
}
}
/**
* Copied from {@link org.exist.util.FileUtils#fileName(Path)}
* as org.exist.start is compiled into a separate Jar and doesn't have
* the rest of eXist available on the classpath
*/
static String fileName(final Path path) {
return path.getFileName().toString();
}
}