From 901d182b8c3bbf6bb31a26348846449911327c1c Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 19:34:07 +0200 Subject: [PATCH 01/20] [refactor] Reformatting code --- .../main/java/org/exist/start/Classpath.java | 83 +++++++------- .../start/CompatibleJavaVersionCheck.java | 44 ++------ .../org/exist/start/LatestFileResolver.java | 37 +++---- .../src/main/java/org/exist/start/Main.java | 103 ++++++++---------- .../main/java/org/exist/start/Version.java | 71 +++++++----- 5 files changed, 156 insertions(+), 182 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/Classpath.java b/exist-start/src/main/java/org/exist/start/Classpath.java index e30302fdd94..ee61343f709 100644 --- a/exist-start/src/main/java/org/exist/start/Classpath.java +++ b/exist-start/src/main/java/org/exist/start/Classpath.java @@ -50,28 +50,40 @@ /** * Class to handle CLASSPATH construction + * * @author Jan Hlavaty */ public class Classpath implements Iterable { final List _elements = new ArrayList<>(); - public Classpath() {} + public Classpath() { + } - public Classpath(final String initial) - { + public Classpath(final String initial) { addClasspath(initial); } - + + /** + * Copied from {@link com.evolvedbinary.j8fu.OptionalUtil#or(Optional, Supplier)} + * as org.exist.start is compiled into a separate Jar and doesn't have + * the rest of eXist available on the classpath + */ + private static Optional or(final Optional left, final Supplier> right) { + if (left.isPresent()) { + return left; + } else { + return right.get(); + } + } + public boolean addComponent(final String component) { if (component != null && !component.isEmpty()) { try { final Path p = Paths.get(component); - if (Files.exists(p)) - { + if (Files.exists(p)) { final Path key = p.toAbsolutePath(); - if (!_elements.contains(key)) - { + if (!_elements.contains(key)) { _elements.add(key); return true; } @@ -82,7 +94,7 @@ public boolean addComponent(final String component) { } return false; } - + public boolean addComponent(final Path component) { if (component != null) { try { @@ -103,12 +115,11 @@ public boolean addComponent(final Path component) { public void addClasspath(final String s) { if (s != null) { final StringTokenizer t = new StringTokenizer(s, File.pathSeparator); - while (t.hasMoreTokens()) - { + while (t.hasMoreTokens()) { addComponent(t.nextToken()); } } - } + } @Override public String toString() { @@ -117,7 +128,7 @@ public String toString() { if (cnt >= 1) { cp.append(_elements.getFirst()); } - for (int i=1; i < cnt; i++) { + for (int i = 1; i < cnt; i++) { cp.append(File.pathSeparatorChar); cp.append(_elements.get(i)); } @@ -125,27 +136,24 @@ public String toString() { } public EXistClassLoader getClassLoader(ClassLoader parent) { - final URL urls[] = _elements - .stream() - .map(Path::toUri) - .map(u -> { - try { - return Optional.of(u.toURL()); - } catch(final MalformedURLException e) { - return Optional.empty(); - } - }).filter(Optional::isPresent) - .map(Optional::get) - .toArray(URL[]::new); + final URL[] urls = _elements.stream().map(Path::toUri).map(u -> { + try { + return Optional.of(u.toURL()); + } catch (final MalformedURLException e) { + return Optional.empty(); + } + }).filter(Optional::isPresent).map(Optional::get).toArray(URL[]::new); // try and ensure we have a classloader parent = or( - or( - or(Optional.ofNullable(parent), () -> Optional.ofNullable(Thread.currentThread().getContextClassLoader())), - () -> Optional.ofNullable(Classpath.class.getClassLoader()) - ), - () -> Optional.ofNullable(ClassLoader.getSystemClassLoader()) - ).orElse(null); + or( + or( + Optional.ofNullable(parent), () -> Optional.ofNullable(Thread.currentThread().getContextClassLoader()) + ), + () -> Optional.ofNullable(Classpath.class.getClassLoader()) + ), + () -> Optional.ofNullable(ClassLoader.getSystemClassLoader()) + ).orElse(null); return new EXistClassLoader(urls, parent); } @@ -154,17 +162,4 @@ public EXistClassLoader getClassLoader(ClassLoader parent) { public Iterator iterator() { return _elements.iterator(); } - - /** - * Copied from {@link com.evolvedbinary.j8fu.OptionalUtil#or(Optional, Supplier)} - * as org.exist.start is compiled into a separate Jar and doesn't have - * the rest of eXist available on the classpath - */ - private static Optional or(final Optional left, final Supplier> right) { - if(left.isPresent()) { - return left; - } else { - return right.get(); - } - } } diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index ed76113e83d..74087cad737 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -29,32 +29,9 @@ public class CompatibleJavaVersionCheck { - private static final IncompatibleJavaVersion[] INCOMPATIBLE_JAVA_VERSIONS = { - IncompatibleJavaVersion(12), - IncompatibleJavaVersion(13), - IncompatibleJavaVersion(14), - IncompatibleJavaVersion(15, 0, 2) - }; - - private static final String INCOMPATIBLE_JAVA_VERSION_NOTICE = - "*****************************************************%n" + - "Warning: Unreliable Java version has been detected!%n" + - "%n" + - "OpenJDK versions 12 through 15.0.1 suffer from a critical%n" + - " bug in the JIT C2 compiler that will cause data loss in%n" + - "eXist-db.%n" + - "%n" + - "The problem has been reported to the OpenJDK community.%n" + - "%n" + - "For more information, see:%n" + - "\t* https://bugs.openjdk.java.net/browse/JDK-8253191%n" + - "\t* https://github.com/eXist-db/exist/issues/3375%n" + - "%n" + - "The detected version of Java on your system is: %s.%n" + - "%n" + - "To prevent potential data loss, eXist-db will not be started.%n" + - "To start eXist-db, we recommend using Java 8 or 11.%n" + - "*****************************************************"; + private static final IncompatibleJavaVersion[] INCOMPATIBLE_JAVA_VERSIONS = {IncompatibleJavaVersion(12), IncompatibleJavaVersion(13), IncompatibleJavaVersion(14), IncompatibleJavaVersion(15, 0, 2)}; + + private static final String INCOMPATIBLE_JAVA_VERSION_NOTICE = "*****************************************************%n" + "Warning: Unreliable Java version has been detected!%n" + "%n" + "OpenJDK versions 12 through 15.0.1 suffer from a critical%n" + " bug in the JIT C2 compiler that will cause data loss in%n" + "eXist-db.%n" + "%n" + "The problem has been reported to the OpenJDK community.%n" + "%n" + "For more information, see:%n" + "\t* https://bugs.openjdk.java.net/browse/JDK-8253191%n" + "\t* https://github.com/eXist-db/exist/issues/3375%n" + "%n" + "The detected version of Java on your system is: %s.%n" + "%n" + "To prevent potential data loss, eXist-db will not be started.%n" + "To start eXist-db, we recommend using Java 8 or 11.%n" + "*****************************************************"; private static final Optional RUNTIME_JAVA_VERSION = Optional.ofNullable(System.getProperty("java.version")); @@ -79,8 +56,10 @@ static void checkForCompatibleJavaVersion(final Optional checkJavaVersio // check for incompatible java version final int[] javaVersionComponents = maybeJavaVersionComponents.get(); final int majorJavaVersion = javaVersionComponents[0]; - /* @Nullable */ final Integer minorJavaVersion = javaVersionComponents.length > 1 ? javaVersionComponents[1] : null; - /* @Nullable */ final Integer patchJavaVersion = javaVersionComponents.length > 2 ? javaVersionComponents[2] : null; + /* @Nullable */ + final Integer minorJavaVersion = javaVersionComponents.length > 1 ? javaVersionComponents[1] : null; + /* @Nullable */ + final Integer patchJavaVersion = javaVersionComponents.length > 2 ? javaVersionComponents[2] : null; for (final IncompatibleJavaVersion incompatibleJavaVersion : INCOMPATIBLE_JAVA_VERSIONS) { // compare major versions @@ -104,7 +83,8 @@ static void checkForCompatibleJavaVersion(final Optional checkJavaVersio } // version is NOT compatible! - throw new StartException(ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED, String.format(INCOMPATIBLE_JAVA_VERSION_NOTICE, RUNTIME_JAVA_VERSION)); + throw new StartException(ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED, + String.format(INCOMPATIBLE_JAVA_VERSION_NOTICE, RUNTIME_JAVA_VERSION)); } // version is compatible @@ -131,17 +111,17 @@ static class IncompatibleJavaVersion { /* @Nullable */ final Integer lessThanMinor; /* @Nullable */ final Integer lessThanPatch; - private IncompatibleJavaVersion(final int major, /* @Nullable */ Integer lessThanMinor, /* @Nullable */ Integer lessThanPatch) { + private IncompatibleJavaVersion(final int major, /* @Nullable */ final Integer lessThanMinor, /* @Nullable */ final Integer lessThanPatch) { this.major = major; this.lessThanMinor = lessThanMinor; this.lessThanPatch = lessThanPatch; } - public static IncompatibleJavaVersion IncompatibleJavaVersion(final int major, /* @Nullable */ Integer lessThanMinor, /* @Nullable */ Integer lessThanPatch) { + public static IncompatibleJavaVersion IncompatibleJavaVersion(final int major, /* @Nullable */ final Integer lessThanMinor, /* @Nullable */ final Integer lessThanPatch) { return new IncompatibleJavaVersion(major, lessThanMinor, lessThanPatch); } - public static IncompatibleJavaVersion IncompatibleJavaVersion(final int major, /* @Nullable */ Integer lessThanMinor) { + public static IncompatibleJavaVersion IncompatibleJavaVersion(final int major, /* @Nullable */ final Integer lessThanMinor) { return IncompatibleJavaVersion(major, lessThanMinor, null); } diff --git a/exist-start/src/main/java/org/exist/start/LatestFileResolver.java b/exist-start/src/main/java/org/exist/start/LatestFileResolver.java index f3e9c36bde7..bad858639fc 100644 --- a/exist-start/src/main/java/org/exist/start/LatestFileResolver.java +++ b/exist-start/src/main/java/org/exist/start/LatestFileResolver.java @@ -32,12 +32,11 @@ /** * This class uses regex pattern matching to find the latest version of a - * particular jar file. - * - * @see LatestFileResolver#getResolvedFileName(String) - * + * particular jar file. + * * @author Ben Schmaus (exist@benschmaus.com) * @version $Revision$ + * @see LatestFileResolver#getResolvedFileName(String) */ public class LatestFileResolver { @@ -45,21 +44,19 @@ public class LatestFileResolver { // latest version of a particular file should be added to the classpath. // E.g., commons-fileupload-%latest%.jar would resolve to something like // commons-fileupload-1.1.jar. - private final static Pattern latestVersionPattern = Pattern.compile( - "(%latest%)" - ); + private final static Pattern latestVersionPattern = Pattern.compile("(%latest%)"); // Set debug mode for each file resolver instance based on whether or // not the system was started with debugging turned on. - private static boolean _debug = Boolean.getBoolean("exist.start.debug"); - + private static final boolean _debug = Boolean.getBoolean("exist.start.debug"); + /** * If the passed file name contains a %latest% token, * find the latest version of that file. Otherwise, return * the passed file name unmodified. - * + * * @param filename Path relative to exist home dir of - * a jar file that should be added to the classpath. + * a jar file that should be added to the classpath. * @return Resolved filename. */ public String getResolvedFileName(final String filename) { @@ -72,9 +69,7 @@ public String getResolvedFileName(final String filename) { final String uptoToken = fileinfo[0]; // Dir that should contain our jar. - final String containerDirName = uptoToken.substring( - 0, uptoToken.lastIndexOf(File.separatorChar) - ); + final String containerDirName = uptoToken.substring(0, uptoToken.lastIndexOf(File.separatorChar)); final Path containerDir = Paths.get(containerDirName); @@ -86,7 +81,7 @@ public String getResolvedFileName(final String filename) { List jars; try { - jars = Main.list(containerDir, p -> { + jars = Main.list(containerDir, p -> { matcher.reset(Main.fileName(p)); return matcher.find(); }); @@ -99,20 +94,14 @@ public String getResolvedFileName(final String filename) { if (!jars.isEmpty()) { final String actualFileName = jars.getFirst().toAbsolutePath().toString(); if (_debug) { - System.err.println( - "Found match: " + actualFileName - + " for jar file pattern: " + filename - ); + System.err.println("Found match: " + actualFileName + " for jar file pattern: " + filename); } return actualFileName; } else { if (_debug) { - System.err.println( - "WARN: No latest version found for JAR file: '" - + filename + "'" - ); + System.err.println("WARN: No latest version found for JAR file: '" + filename + "'"); } } return filename; - } + } } diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 59475223b47..9f6a12f8a97 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -38,14 +38,15 @@ */ package org.exist.start; -import java.io.*; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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.List; +import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -67,29 +68,30 @@ public class Main { public static final String STANDALONE_ENABLED_JETTY_CONFIGS = "standalone.enabled-jetty-configs"; public static final String PROP_LOG4J_DISABLEJMX = "log4j2.disableJmx"; public static final String PROP_XML_CATALOG_ALWAYS_RESOLVE = "xml.catalog.alwaysResolve"; - - private static final int ERROR_CODE_GENERAL = 1; - private static final int ERROR_CODE_NO_JETTY_CONFIG = 7; - static final int ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED = 13; - public static final String CONFIG_DIR_NAME = "etc"; - - private static final String PROP_EXIST_START_DEBUG = "exist.start.debug"; public static final String PROP_EXIST_JETTY_CONFIG = "exist.jetty.config"; public static final String PROP_EXIST_HOME = "exist.home"; public static final String PROP_JETTY_HOME = "jetty.home"; - private static final String PROP_LOG4J_CONFIGURATION_FILE = "log4j.configurationFile"; - private static final String PROP_JUL_MANAGER = "java.util.logging.manager"; - private static final String PROP_JAVA_TEMP_DIR = "java.io.tmpdir"; - public static final String ENV_EXIST_JETTY_CONFIG = "EXIST_JETTY_CONFIG"; public static final String ENV_EXIST_HOME = "EXIST_HOME"; public static final String ENV_JETTY_HOME = "JETTY_HOME"; - + static final int ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED = 13; + private static final int ERROR_CODE_GENERAL = 1; + private static final int ERROR_CODE_NO_JETTY_CONFIG = 7; + private static final String PROP_EXIST_START_DEBUG = "exist.start.debug"; + private static final String PROP_LOG4J_CONFIGURATION_FILE = "log4j.configurationFile"; + private static final String PROP_JUL_MANAGER = "java.util.logging.manager"; + private static final String PROP_JAVA_TEMP_DIR = "java.io.tmpdir"; private static Main exist; - + private final boolean _debug = Boolean.getBoolean(PROP_EXIST_START_DEBUG); private String _mode = "jetty"; - private boolean _debug = Boolean.getBoolean(PROP_EXIST_START_DEBUG); + + private Main() { + } + + public Main(final String mode) { + this._mode = mode; + } public static void main(final String[] args) { try { @@ -113,17 +115,6 @@ public static Main getMain() { return exist; } - public String getMode() { - return this._mode; - } - - private Main() { - } - - public Main(final String mode) { - this._mode = mode; - } - private static Path getDirectory(final String name) { try { if (name != null) { @@ -138,9 +129,7 @@ private static Path getDirectory(final String name) { return null; } - private static void invokeMain(final ClassLoader classloader, final String classname, final String[] args) - throws IllegalAccessException, InvocationTargetException, - NoSuchMethodException, ClassNotFoundException { + private static void invokeMain(final ClassLoader classloader, final String classname, final String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { final Class invoked_class = classloader.loadClass(classname); @@ -154,6 +143,30 @@ private static void invokeMain(final ClassLoader classloader, final String class main.invoke(null, method_params); } + /** + * 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 list(final Path directory, final Predicate filter) throws IOException { + try (final Stream 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(); + } + + public String getMode() { + return this._mode; + } + public void run(final String[] args) { try { runEx(args); @@ -165,8 +178,6 @@ public void run(final String[] args) { } } - - public void runEx(String[] args) throws StartException { // Check if the OpenJDK version can corrupt eXist-db @@ -270,14 +281,15 @@ public void runEx(String[] args) throws StartException { if (log4jConfigurationFile.isPresent()) { //redirect JUL to log4j2 unless otherwise specified - System.setProperty(PROP_JUL_MANAGER, Optional.ofNullable(System.getProperty(PROP_JUL_MANAGER)).orElse("org.apache.logging.log4j.jul.LogManager")); + System.setProperty(PROP_JUL_MANAGER, + Optional.ofNullable(System.getProperty(PROP_JUL_MANAGER)).orElse("org.apache.logging.log4j.jul.LogManager")); } // Enable JXM support log4j since v2.24.0 [2024] System.setProperty(PROP_LOG4J_DISABLEJMX, "false"); // Modify behavior XML resolver for > 5.x [2024] - System.setProperty(PROP_XML_CATALOG_ALWAYS_RESOLVE,"false"); + System.setProperty(PROP_XML_CATALOG_ALWAYS_RESOLVE, "false"); // clean up tempdir for Jetty... try { @@ -339,28 +351,9 @@ public void shutdownEx() throws StopException { final Method stopAll = brokerPool.getDeclaredMethod("stopAll", boolean.class); stopAll.setAccessible(true); stopAll.invoke(null, false); - } catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + } catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException e) { throw new StopException(e.getMessage(), e); } } - - /** - * 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 list(final Path directory, final Predicate filter) throws IOException { - try(final Stream 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(); - } } diff --git a/exist-start/src/main/java/org/exist/start/Version.java b/exist-start/src/main/java/org/exist/start/Version.java index 3d83922dfda..4f3b13a872f 100644 --- a/exist-start/src/main/java/org/exist/start/Version.java +++ b/exist-start/src/main/java/org/exist/start/Version.java @@ -41,6 +41,7 @@ /** * Utility class for parsing and comparing version strings. * JDK 1.1 compatible. + * * @author Jan Hlavatý */ public class Version { @@ -48,24 +49,24 @@ public class Version { private static final int INFERIOR = -1; private static final int EQUAL = 0; private static final int SUPERIOR = 1; - + int _version = 0; int _revision = 0; int _subrevision = 0; String _suffix = ""; - + public Version() { } - + public Version(final String version_string) { parse(version_string); } - + /** * parses version string in the form version[.revision[.subrevision[extension]]] * into this instance. * - * @param version_string Text representation of a version. + * @param version_string Text representation of a version. */ public void parse(final String version_string) { _version = 0; @@ -75,64 +76,80 @@ public void parse(final String version_string) { int pos = 0; int startpos = 0; final int endpos = version_string.length(); - while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) { + while ((pos < endpos) && Character.isDigit(version_string.charAt(pos))) { pos++; } - _version = Integer.parseInt(version_string.substring(startpos,pos)); - if ((pos < endpos) && version_string.charAt(pos)=='.') { + _version = Integer.parseInt(version_string.substring(startpos, pos)); + if ((pos < endpos) && version_string.charAt(pos) == '.') { startpos = ++pos; - while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) { + while ((pos < endpos) && Character.isDigit(version_string.charAt(pos))) { pos++; } - _revision = Integer.parseInt(version_string.substring(startpos,pos)); + _revision = Integer.parseInt(version_string.substring(startpos, pos)); } - if ((pos < endpos) && version_string.charAt(pos)=='.') { + if ((pos < endpos) && version_string.charAt(pos) == '.') { startpos = ++pos; - while ( (pos < endpos) && Character.isDigit(version_string.charAt(pos))) { + while ((pos < endpos) && Character.isDigit(version_string.charAt(pos))) { pos++; } - _subrevision = Integer.parseInt(version_string.substring(startpos,pos)); + _subrevision = Integer.parseInt(version_string.substring(startpos, pos)); } if (pos < endpos) { _suffix = version_string.substring(pos); } } - + /** * @return string representation of this version */ public String toString() { return String.valueOf(_version) + '.' + _revision + '.' + _subrevision + _suffix; } - + // java.lang.Comparable is Java 1.2! Cannot use it + /** * Compares with other version. Does not take extension into account, * as there is no reliable way to order them. + * * @param other Version object to be compared. * @return Constants.INFERIOR if this is older version that other, - * Constants.EQUAL if its same version, - * Constants.SUPERIOR if it's newer version than other + * Constants.EQUAL if its same version, + * Constants.SUPERIOR if it's newer version than other */ public int compare(final Version other) { - if (other == null) {throw new NullPointerException("other version is null");} - if (this._version < other._version) {return INFERIOR;} - if (this._version > other._version) {return SUPERIOR;} - if (this._revision < other._revision) {return INFERIOR;} - if (this._revision > other._revision) {return SUPERIOR;} - if (this._subrevision < other._subrevision) {return INFERIOR;} - if (this._subrevision > other._subrevision) {return SUPERIOR;} + if (other == null) { + throw new NullPointerException("other version is null"); + } + if (this._version < other._version) { + return INFERIOR; + } + if (this._version > other._version) { + return SUPERIOR; + } + if (this._revision < other._revision) { + return INFERIOR; + } + if (this._revision > other._revision) { + return SUPERIOR; + } + if (this._subrevision < other._subrevision) { + return INFERIOR; + } + if (this._subrevision > other._subrevision) { + return SUPERIOR; + } return EQUAL; } - + /** * Check whether this version is in range of versions specified * * @param high Highest version, inclusive. - * @param low Lowest version, inclusive. + * @param low Lowest version, inclusive. * @return TRUE if Version is between high and low (inclusive), otherwise FALSE. */ public boolean isInRange(final Version low, final Version high) { - return (compare(low)>=0 && compare(high)<=0); + return (compare(low) >= 0 && compare(high) <= 0); } } From bd1baa7576fab3b36b968a0c354c86ea9b549541 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 19:41:33 +0200 Subject: [PATCH 02/20] [refactor] move class and methods to jnlp package --- .../java/org/exist/webstart/JnlpJarFiles.java | 1 - .../exist/webstart}/LatestFileResolver.java | 30 +++++++++++++++++-- .../src/main/java/org/exist/start/Main.java | 20 ------------- 3 files changed, 27 insertions(+), 24 deletions(-) rename {exist-start/src/main/java/org/exist/start => exist-core/src/main/java/org/exist/webstart}/LatestFileResolver.java (80%) diff --git a/exist-core/src/main/java/org/exist/webstart/JnlpJarFiles.java b/exist-core/src/main/java/org/exist/webstart/JnlpJarFiles.java index 71fb193204a..585dccc95fa 100644 --- a/exist-core/src/main/java/org/exist/webstart/JnlpJarFiles.java +++ b/exist-core/src/main/java/org/exist/webstart/JnlpJarFiles.java @@ -33,7 +33,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.exist.start.LatestFileResolver; import org.exist.util.FileUtils; /** diff --git a/exist-start/src/main/java/org/exist/start/LatestFileResolver.java b/exist-core/src/main/java/org/exist/webstart/LatestFileResolver.java similarity index 80% rename from exist-start/src/main/java/org/exist/start/LatestFileResolver.java rename to exist-core/src/main/java/org/exist/webstart/LatestFileResolver.java index bad858639fc..f5708f10c28 100644 --- a/exist-start/src/main/java/org/exist/start/LatestFileResolver.java +++ b/exist-core/src/main/java/org/exist/webstart/LatestFileResolver.java @@ -19,16 +19,20 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.start; +package org.exist.webstart; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * This class uses regex pattern matching to find the latest version of a @@ -81,8 +85,8 @@ public String getResolvedFileName(final String filename) { List jars; try { - jars = Main.list(containerDir, p -> { - matcher.reset(Main.fileName(p)); + jars = list(containerDir, p -> { + matcher.reset(fileName(p)); return matcher.find(); }); } catch (final IOException e) { @@ -104,4 +108,24 @@ public String getResolvedFileName(final String filename) { } return filename; } + + /** + * 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 list(final Path directory, final Predicate filter) throws IOException { + try (final Stream 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(); + } } diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 9f6a12f8a97..8bfd88f5389 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -143,26 +143,6 @@ private static void invokeMain(final ClassLoader classloader, final String class main.invoke(null, method_params); } - /** - * 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 list(final Path directory, final Predicate filter) throws IOException { - try (final Stream 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(); - } - public String getMode() { return this._mode; } From 2e68797bc60679fc748e5a3af7d7bcfa83bbd92c Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 21:05:59 +0200 Subject: [PATCH 03/20] [refactor] update Java version checker class --- .../start/CompatibleJavaVersionCheck.java | 43 +++++++++++++++++-- .../start/CompatibleJavaVersionCheckTest.java | 10 +++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index 74087cad737..9fe6472129b 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -27,11 +27,46 @@ import static org.exist.start.CompatibleJavaVersionCheck.IncompatibleJavaVersion.IncompatibleJavaVersion; import static org.exist.start.Main.ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED; +/** + * Helper class for checking OpenJDK compatibility. + * + * eXist-db has been compiled with Java21 (state of 2025Q2). + * + * Older versions of Java contained a number of serious compiler bugs that caused database corruptions. + * These problematic versions are deprecated and therefore this class is not relevant anymore. + * + * The code is kept for archival purposes and potential future re-usage. + * + * ---------------------------- + * + * OpenJDK versions 12 through 15.0.1 suffer from a critical bug in the JIT C2 compiler that will + * cause data loss in eXist-db. The problem has been reported to the OpenJDK community. + * + * For more information, see: + * - https://bugs.openjdk.java.net/browse/JDK-8253191 + * - https://github.com/eXist-db/exist/issues/3375 + * + * + */ public class CompatibleJavaVersionCheck { - private static final IncompatibleJavaVersion[] INCOMPATIBLE_JAVA_VERSIONS = {IncompatibleJavaVersion(12), IncompatibleJavaVersion(13), IncompatibleJavaVersion(14), IncompatibleJavaVersion(15, 0, 2)}; - - private static final String INCOMPATIBLE_JAVA_VERSION_NOTICE = "*****************************************************%n" + "Warning: Unreliable Java version has been detected!%n" + "%n" + "OpenJDK versions 12 through 15.0.1 suffer from a critical%n" + " bug in the JIT C2 compiler that will cause data loss in%n" + "eXist-db.%n" + "%n" + "The problem has been reported to the OpenJDK community.%n" + "%n" + "For more information, see:%n" + "\t* https://bugs.openjdk.java.net/browse/JDK-8253191%n" + "\t* https://github.com/eXist-db/exist/issues/3375%n" + "%n" + "The detected version of Java on your system is: %s.%n" + "%n" + "To prevent potential data loss, eXist-db will not be started.%n" + "To start eXist-db, we recommend using Java 8 or 11.%n" + "*****************************************************"; + private static final IncompatibleJavaVersion[] INCOMPATIBLE_JAVA_VERSIONS = { + IncompatibleJavaVersion(12), + IncompatibleJavaVersion(13), + IncompatibleJavaVersion(14), + IncompatibleJavaVersion(15, 0, 2), + }; + + private static final String INCOMPATIBLE_JAVA_VERSION_NOTICE = + "*****************************************************%n" + + "Incorrect version of Java detected!%n" + + "%n" + + "The detected version of Java on your system is: %s.%n" + + "%n" + + "eXist-db has been developed and qualified using Java 21.%n" + + "%n" + + "Newer versions of Java might or might not work correctly.%n" + + "*****************************************************"; private static final Optional RUNTIME_JAVA_VERSION = Optional.ofNullable(System.getProperty("java.version")); @@ -84,7 +119,7 @@ static void checkForCompatibleJavaVersion(final Optional checkJavaVersio // version is NOT compatible! throw new StartException(ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED, - String.format(INCOMPATIBLE_JAVA_VERSION_NOTICE, RUNTIME_JAVA_VERSION)); + String.format(INCOMPATIBLE_JAVA_VERSION_NOTICE, RUNTIME_JAVA_VERSION.orElse("UKNOWN"))); } // version is compatible diff --git a/exist-start/src/test/java/org/exist/start/CompatibleJavaVersionCheckTest.java b/exist-start/src/test/java/org/exist/start/CompatibleJavaVersionCheckTest.java index e3af0d2efcc..17100dea3be 100644 --- a/exist-start/src/test/java/org/exist/start/CompatibleJavaVersionCheckTest.java +++ b/exist-start/src/test/java/org/exist/start/CompatibleJavaVersionCheckTest.java @@ -211,4 +211,14 @@ public void checkJava15_0_2() throws StartException { public void checkJava15_0_3() throws StartException { CompatibleJavaVersionCheck.checkForCompatibleJavaVersion(Optional.of("15.0.3")); } + + @Test + public void checkJava21() throws StartException { + CompatibleJavaVersionCheck.checkForCompatibleJavaVersion(Optional.of("21.0.6")); + } + + @Test + public void checkJava25() throws StartException { + CompatibleJavaVersionCheck.checkForCompatibleJavaVersion(Optional.of("25.0.1")); + } } From 2c1285e94cd101dad745ed74aa5236ce2d1a2a5a Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 21:22:28 +0200 Subject: [PATCH 04/20] [refactor] remove unused class --- .../main/java/org/exist/start/Version.java | 155 ------------------ 1 file changed, 155 deletions(-) delete mode 100644 exist-start/src/main/java/org/exist/start/Version.java diff --git a/exist-start/src/main/java/org/exist/start/Version.java b/exist-start/src/main/java/org/exist/start/Version.java deleted file mode 100644 index 4f3b13a872f..00000000000 --- a/exist-start/src/main/java/org/exist/start/Version.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * NOTE: This file is in part based on code from Mort Bay Consulting. - * The original license statement is also included below. - * - * eXist-db Open Source Native XML Database - * Copyright (C) 2001 The eXist-db Authors - * - * info@exist-db.org - * http://www.exist-db.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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * --------------------------------------------------------------------- - * - * Copyright 2002-2005 Mort Bay Consulting Pty. Ltd. - * - * Licensed 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.exist.start; - -/** - * Utility class for parsing and comparing version strings. - * JDK 1.1 compatible. - * - * @author Jan Hlavatý - */ -public class Version { - - private static final int INFERIOR = -1; - private static final int EQUAL = 0; - private static final int SUPERIOR = 1; - - int _version = 0; - int _revision = 0; - int _subrevision = 0; - String _suffix = ""; - - public Version() { - } - - public Version(final String version_string) { - parse(version_string); - } - - /** - * parses version string in the form version[.revision[.subrevision[extension]]] - * into this instance. - * - * @param version_string Text representation of a version. - */ - public void parse(final String version_string) { - _version = 0; - _revision = 0; - _subrevision = 0; - _suffix = ""; - int pos = 0; - int startpos = 0; - final int endpos = version_string.length(); - while ((pos < endpos) && Character.isDigit(version_string.charAt(pos))) { - pos++; - } - _version = Integer.parseInt(version_string.substring(startpos, pos)); - if ((pos < endpos) && version_string.charAt(pos) == '.') { - startpos = ++pos; - while ((pos < endpos) && Character.isDigit(version_string.charAt(pos))) { - pos++; - } - _revision = Integer.parseInt(version_string.substring(startpos, pos)); - } - if ((pos < endpos) && version_string.charAt(pos) == '.') { - startpos = ++pos; - while ((pos < endpos) && Character.isDigit(version_string.charAt(pos))) { - pos++; - } - _subrevision = Integer.parseInt(version_string.substring(startpos, pos)); - } - if (pos < endpos) { - _suffix = version_string.substring(pos); - } - } - - /** - * @return string representation of this version - */ - public String toString() { - return String.valueOf(_version) + '.' + _revision + '.' + _subrevision + _suffix; - } - - // java.lang.Comparable is Java 1.2! Cannot use it - - /** - * Compares with other version. Does not take extension into account, - * as there is no reliable way to order them. - * - * @param other Version object to be compared. - * @return Constants.INFERIOR if this is older version that other, - * Constants.EQUAL if its same version, - * Constants.SUPERIOR if it's newer version than other - */ - public int compare(final Version other) { - if (other == null) { - throw new NullPointerException("other version is null"); - } - if (this._version < other._version) { - return INFERIOR; - } - if (this._version > other._version) { - return SUPERIOR; - } - if (this._revision < other._revision) { - return INFERIOR; - } - if (this._revision > other._revision) { - return SUPERIOR; - } - if (this._subrevision < other._subrevision) { - return INFERIOR; - } - if (this._subrevision > other._subrevision) { - return SUPERIOR; - } - return EQUAL; - } - - /** - * Check whether this version is in range of versions specified - * - * @param high Highest version, inclusive. - * @param low Lowest version, inclusive. - * @return TRUE if Version is between high and low (inclusive), otherwise FALSE. - */ - public boolean isInRange(final Version low, final Version high) { - return (compare(low) >= 0 && compare(high) <= 0); - } -} From 2538c4a684d64ddc92f05e61ffa6c0cecceff3c2 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 21:29:41 +0200 Subject: [PATCH 05/20] [refactor] Isolate classloader in separate package as it is externally used too --- .../src/main/java/org/exist/repo/ClasspathHelper.java | 4 ++-- .../src/main/java/org/exist/test/ExistEmbeddedServer.java | 4 ++-- exist-start/pom.xml | 3 +-- exist-start/src/main/java/org/exist/start/Main.java | 8 +++----- .../java/org/exist/start/{ => classloader}/Classpath.java | 2 +- .../exist/start/{ => classloader}/EXistClassLoader.java | 2 +- 6 files changed, 10 insertions(+), 13 deletions(-) rename exist-start/src/main/java/org/exist/start/{ => classloader}/Classpath.java (99%) rename exist-start/src/main/java/org/exist/start/{ => classloader}/EXistClassLoader.java (97%) diff --git a/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java b/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java index 8aa2a99a0fd..1a1d016afe6 100644 --- a/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java +++ b/exist-core/src/main/java/org/exist/repo/ClasspathHelper.java @@ -24,8 +24,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.SystemProperties; -import org.exist.start.Classpath; -import org.exist.start.EXistClassLoader; +import org.exist.start.classloader.Classpath; +import org.exist.start.classloader.EXistClassLoader; import org.exist.storage.BrokerPool; import org.exist.storage.BrokerPoolService; import org.expath.pkg.repo.FileSystemStorage; diff --git a/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java b/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java index 108a6d4be6b..b5ea7f38033 100644 --- a/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java +++ b/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java @@ -24,8 +24,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.EXistException; -import org.exist.start.Classpath; -import org.exist.start.EXistClassLoader; +import org.exist.start.classloader.Classpath; +import org.exist.start.classloader.EXistClassLoader; import org.exist.storage.BrokerPool; import org.exist.storage.journal.Journal; import org.exist.util.Configuration; diff --git a/exist-start/pom.xml b/exist-start/pom.xml index f07d113ebff..34ff4692b3b 100644 --- a/exist-start/pom.xml +++ b/exist-start/pom.xml @@ -76,9 +76,8 @@ --> Mortbay-APACHE-2-license.template.txt - src/main/java/org/exist/start/Classpath.java + src/main/java/org/exist/start/classloader/Classpath.java src/main/java/org/exist/start/Main.java - src/main/java/org/exist/start/Version.java diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 8bfd88f5389..0df6193f5eb 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -38,18 +38,16 @@ */ package org.exist.start; -import java.io.IOException; +import org.exist.start.classloader.Classpath; +import org.exist.start.classloader.EXistClassLoader; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * This is an adopted version of the corresponding classes shipped diff --git a/exist-start/src/main/java/org/exist/start/Classpath.java b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java similarity index 99% rename from exist-start/src/main/java/org/exist/start/Classpath.java rename to exist-start/src/main/java/org/exist/start/classloader/Classpath.java index ee61343f709..c4634ef8438 100644 --- a/exist-start/src/main/java/org/exist/start/Classpath.java +++ b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java @@ -36,7 +36,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.exist.start; +package org.exist.start.classloader; import java.io.File; import java.net.MalformedURLException; diff --git a/exist-start/src/main/java/org/exist/start/EXistClassLoader.java b/exist-start/src/main/java/org/exist/start/classloader/EXistClassLoader.java similarity index 97% rename from exist-start/src/main/java/org/exist/start/EXistClassLoader.java rename to exist-start/src/main/java/org/exist/start/classloader/EXistClassLoader.java index 03964bc6374..dd1ed724653 100644 --- a/exist-start/src/main/java/org/exist/start/EXistClassLoader.java +++ b/exist-start/src/main/java/org/exist/start/classloader/EXistClassLoader.java @@ -19,7 +19,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package org.exist.start; +package org.exist.start.classloader; import java.net.MalformedURLException; import java.net.URL; From e06ff495fc06943a85dcbe8ef72b8beb7e7e38ae Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 21:48:52 +0200 Subject: [PATCH 06/20] [refactor] improve javadoc --- .../start/CompatibleJavaVersionCheck.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index 9fe6472129b..9bc7e3a5d96 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -29,22 +29,22 @@ /** * Helper class for checking OpenJDK compatibility. - * + *

* eXist-db has been compiled with Java21 (state of 2025Q2). - * + *

* Older versions of Java contained a number of serious compiler bugs that caused database corruptions. * These problematic versions are deprecated and therefore this class is not relevant anymore. - * + *

* The code is kept for archival purposes and potential future re-usage. - * + *

* ---------------------------- - * + *

* OpenJDK versions 12 through 15.0.1 suffer from a critical bug in the JIT C2 compiler that will * cause data loss in eXist-db. The problem has been reported to the OpenJDK community. - * - * For more information, see: - * - https://bugs.openjdk.java.net/browse/JDK-8253191 - * - https://github.com/eXist-db/exist/issues/3375 + *

+ * For more...a.net/* - https://github.com/eXist-db/exist/issues/3375 * * */ From a0a7027172a68d9e13778ac998777bf6e7ecc0bf Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 21:56:39 +0200 Subject: [PATCH 07/20] [refactor] simplify code --- .../start/CompatibleJavaVersionCheck.java | 2 +- .../src/main/java/org/exist/start/Main.java | 8 +++--- .../exist/start/classloader/Classpath.java | 28 ++++++------------- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index 9bc7e3a5d96..0b8126d1538 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -83,7 +83,7 @@ public static void checkForCompatibleJavaVersion() throws StartException { static void checkForCompatibleJavaVersion(final Optional checkJavaVersion) throws StartException { final Optional maybeJavaVersionComponents = extractJavaVersionComponents(checkJavaVersion); - if (!maybeJavaVersionComponents.isPresent()) { + if (maybeJavaVersionComponents.isEmpty()) { // Could not determine major java version, so best to let the user proceed... return; } diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 0df6193f5eb..259d3f28d28 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -217,7 +217,7 @@ public void runEx(String[] args) throws StartException { final Optional jettyHomeDir = getFromSysPropOrEnv(PROP_JETTY_HOME, ENV_JETTY_HOME).map(Paths::get); Optional existJettyConfigFile = getFromSysPropOrEnv(PROP_EXIST_JETTY_CONFIG, ENV_EXIST_JETTY_CONFIG).map(Paths::get); - if (!existJettyConfigFile.isPresent()) { + if (existJettyConfigFile.isEmpty()) { final String config; if ("jetty".equals(_mode)) { config = STANDARD_ENABLED_JETTY_CONFIGS; @@ -233,7 +233,7 @@ public void runEx(String[] args) throws StartException { existJettyConfigFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve(config)); } - if (!existJettyConfigFile.isPresent()) { + if (existJettyConfigFile.isEmpty()) { System.err.println("ERROR: jetty config file could not be found! Make sure to set exist.jetty.config or EXIST_JETTY_CONFIG."); System.err.flush(); throw new StartException(ERROR_CODE_NO_JETTY_CONFIG); @@ -247,7 +247,7 @@ public void runEx(String[] args) throws StartException { // find log4j2.xml Optional log4jConfigurationFile = Optional.ofNullable(System.getProperty(PROP_LOG4J_CONFIGURATION_FILE)).map(Paths::get); - if (!log4jConfigurationFile.isPresent()) { + if (log4jConfigurationFile.isEmpty()) { if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(CONFIG_DIR_NAME))) { log4jConfigurationFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve("log4j2.xml")); } @@ -300,7 +300,7 @@ public void runEx(String[] args) throws StartException { private Optional getFromSysPropOrEnv(final String sysPropName, final String envVarName) { Optional value = Optional.ofNullable(System.getProperty(sysPropName)); - if (!value.isPresent()) { + if (value.isEmpty()) { value = Optional.ofNullable(System.getenv().get(envVarName)); // if we managed to detect from environment, store it in a system property value.ifPresent(s -> System.setProperty(sysPropName, s)); diff --git a/exist-start/src/main/java/org/exist/start/classloader/Classpath.java b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java index c4634ef8438..98ce270ed8d 100644 --- a/exist-start/src/main/java/org/exist/start/classloader/Classpath.java +++ b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java @@ -80,14 +80,8 @@ private static Optional or(final Optional left, final Supplier Date: Sun, 13 Apr 2025 21:57:04 +0200 Subject: [PATCH 08/20] [refactor] update javadoc --- .../main/java/org/exist/start/CompatibleJavaVersionCheck.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index 0b8126d1538..4be3f5c2f8b 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -43,8 +43,8 @@ * cause data loss in eXist-db. The problem has been reported to the OpenJDK community. *

* For more...a.net/* - https://github.com/eXist-db/exist/issues/3375 + * - C2: Masked byte comparisons with large masks produce wrong result on x86 + * - eXist-db does not run correctly on JDKs 12, 13, 14 and 15 #3375 * * */ From 326bb59310699b32046ab1161e5c2bddeb5c654d Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 21:57:37 +0200 Subject: [PATCH 09/20] [refactor] order declarations for readability --- .../src/main/java/org/exist/start/Main.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 259d3f28d28..b89fe5d20ef 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -62,24 +62,26 @@ * - include entries from lib _when needed_ */ public class Main { - public static final String STANDARD_ENABLED_JETTY_CONFIGS = "standard.enabled-jetty-configs"; - public static final String STANDALONE_ENABLED_JETTY_CONFIGS = "standalone.enabled-jetty-configs"; - public static final String PROP_LOG4J_DISABLEJMX = "log4j2.disableJmx"; - public static final String PROP_XML_CATALOG_ALWAYS_RESOLVE = "xml.catalog.alwaysResolve"; + public static final String CONFIG_DIR_NAME = "etc"; - public static final String PROP_EXIST_JETTY_CONFIG = "exist.jetty.config"; - public static final String PROP_EXIST_HOME = "exist.home"; - public static final String PROP_JETTY_HOME = "jetty.home"; - public static final String ENV_EXIST_JETTY_CONFIG = "EXIST_JETTY_CONFIG"; public static final String ENV_EXIST_HOME = "EXIST_HOME"; + public static final String ENV_EXIST_JETTY_CONFIG = "EXIST_JETTY_CONFIG"; public static final String ENV_JETTY_HOME = "JETTY_HOME"; + public static final String PROP_EXIST_HOME = "exist.home"; + public static final String PROP_EXIST_JETTY_CONFIG = "exist.jetty.config"; + public static final String PROP_JETTY_HOME = "jetty.home"; + public static final String PROP_LOG4J_DISABLEJMX = "log4j2.disableJmx"; + public static final String PROP_XML_CATALOG_ALWAYS_RESOLVE = "xml.catalog.alwaysResolve"; + public static final String STANDALONE_ENABLED_JETTY_CONFIGS = "standalone.enabled-jetty-configs"; + public static final String STANDARD_ENABLED_JETTY_CONFIGS = "standard.enabled-jetty-configs"; static final int ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED = 13; private static final int ERROR_CODE_GENERAL = 1; private static final int ERROR_CODE_NO_JETTY_CONFIG = 7; private static final String PROP_EXIST_START_DEBUG = "exist.start.debug"; - private static final String PROP_LOG4J_CONFIGURATION_FILE = "log4j.configurationFile"; - private static final String PROP_JUL_MANAGER = "java.util.logging.manager"; private static final String PROP_JAVA_TEMP_DIR = "java.io.tmpdir"; + private static final String PROP_JUL_MANAGER = "java.util.logging.manager"; + private static final String PROP_LOG4J_CONFIGURATION_FILE = "log4j.configurationFile"; + private static Main exist; private final boolean _debug = Boolean.getBoolean(PROP_EXIST_START_DEBUG); private String _mode = "jetty"; From a73b14c79a7f3755fe8f3f256a571f9fb696b039 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 22:00:16 +0200 Subject: [PATCH 10/20] [bugfix] make sure that for macos also java21 is enforced --- exist-distribution/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exist-distribution/pom.xml b/exist-distribution/pom.xml index f41bcd8413d..46de9acfdc5 100644 --- a/exist-distribution/pom.xml +++ b/exist-distribution/pom.xml @@ -1057,7 +1057,7 @@ org.exist.start.Main org.exist.start.Main eXist-JavaAppLauncher - 1.8 + 21 10.9 ${project.version} ${project.version} From b5065c30f96552aa776b2a6ac7df0abc733e2d3b Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sun, 13 Apr 2025 23:44:09 +0200 Subject: [PATCH 11/20] [refactor] improve readability of startup classes --- .../java/org/exist/service/ExistDbDaemon.java | 4 +- .../src/main/java/org/exist/start/Main.java | 279 +++++++++--------- .../exist/start/classloader/Classpath.java | 23 +- 3 files changed, 160 insertions(+), 146 deletions(-) diff --git a/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java b/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java index d9e2a2300ba..394fd5255e6 100644 --- a/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java +++ b/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java @@ -65,12 +65,12 @@ public void start() throws Exception { runArgs[0] = MODE_JETTY; System.arraycopy(args, 0, runArgs, 1, args.length); - this.main.runEx(runArgs); + this.main.startExistdb(runArgs); } @Override public void stop() throws Exception { - this.main.shutdownEx(); + this.main.shutdownExistdb(); } @Override diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index b89fe5d20ef..951609f4795 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -74,6 +74,11 @@ public class Main { public static final String PROP_XML_CATALOG_ALWAYS_RESOLVE = "xml.catalog.alwaysResolve"; public static final String STANDALONE_ENABLED_JETTY_CONFIGS = "standalone.enabled-jetty-configs"; public static final String STANDARD_ENABLED_JETTY_CONFIGS = "standard.enabled-jetty-configs"; + public static final String MODE_JETTY = "jetty"; + public static final String MODE_STANDALONE = "standalone"; + public static final String MODE_OTHER = "other"; + public static final String MODE_CLIENT = "client"; + public static final String MODE_BACKUP = "backup"; static final int ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED = 13; private static final int ERROR_CODE_GENERAL = 1; private static final int ERROR_CODE_NO_JETTY_CONFIG = 7; @@ -81,10 +86,9 @@ public class Main { private static final String PROP_JAVA_TEMP_DIR = "java.io.tmpdir"; private static final String PROP_JUL_MANAGER = "java.util.logging.manager"; private static final String PROP_LOG4J_CONFIGURATION_FILE = "log4j.configurationFile"; - private static Main exist; - private final boolean _debug = Boolean.getBoolean(PROP_EXIST_START_DEBUG); - private String _mode = "jetty"; + private final boolean inDebugMode = Boolean.getBoolean(PROP_EXIST_START_DEBUG); + private String _mode = MODE_JETTY; private Main() { } @@ -115,21 +119,8 @@ public static Main getMain() { return exist; } - private 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; - } - - private static void invokeMain(final ClassLoader classloader, final String classname, final String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { + private static void invokeMain(final ClassLoader classloader, final String classname, final String[] args) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { final Class invoked_class = classloader.loadClass(classname); @@ -143,13 +134,48 @@ private static void invokeMain(final ClassLoader classloader, final String class main.invoke(null, method_params); } - public String getMode() { - return this._mode; + private static EXistClassLoader getEXistClassLoader() { + final Classpath _classpath = new Classpath(); + final EXistClassLoader eXistClassLoader = _classpath.getClassLoader(null); + Thread.currentThread().setContextClassLoader(eXistClassLoader); + return eXistClassLoader; + } + + private static void setupLog4j2(final Optional existHomeDir) { + // find log4j2.xml + Optional log4jConfigurationFile = Optional.ofNullable(System.getProperty(PROP_LOG4J_CONFIGURATION_FILE)).map(Paths::get); + if (log4jConfigurationFile.isEmpty()) { + if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(CONFIG_DIR_NAME))) { + log4jConfigurationFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve("log4j2.xml")); + } + + if (log4jConfigurationFile.isPresent() && Files.isReadable(log4jConfigurationFile.get())) { + System.setProperty(PROP_LOG4J_CONFIGURATION_FILE, log4jConfigurationFile.get().toAbsolutePath().toString()); + } + } + + if (log4jConfigurationFile.isPresent()) { + //redirect JUL to log4j2 unless otherwise specified + System.setProperty(PROP_JUL_MANAGER, Optional + .ofNullable(System.getProperty(PROP_JUL_MANAGER)) + .orElse("org.apache.logging.log4j.jul.LogManager")); + } + + // Enable JMX support log4j since v2.24.0 [2024] + System.setProperty(PROP_LOG4J_DISABLEJMX, "false"); + } + + private String getMode() { + return _mode; + } + + private void setMode(final String mode) { + _mode = mode; } public void run(final String[] args) { try { - runEx(args); + startExistdb(args); } catch (final StartException e) { if (e.getMessage() != null && !e.getMessage().isEmpty()) { System.err.println(e.getMessage()); @@ -158,120 +184,95 @@ public void run(final String[] args) { } } - public void runEx(String[] args) throws StartException { + public void startExistdb(String[] args) throws StartException { // Check if the OpenJDK version can corrupt eXist-db CompatibleJavaVersionCheck.checkForCompatibleJavaVersion(); final String _classname; if (args.length > 0) { - switch (args[0]) { - case "client" -> { - _classname = "org.exist.client.InteractiveClient"; - _mode = "client"; - } - case "backup" -> { - _classname = "org.exist.backup.Main"; - _mode = "backup"; - } - case "jetty", "standalone" -> { - _classname = "org.exist.jetty.JettyStart"; - _mode = args[0]; - } - case "launch" -> { - _classname = "org.exist.launcher.LauncherWrapper"; - _mode = "jetty"; - } - case "launcher" -> { - _classname = "org.exist.launcher.LauncherWrapper"; - _mode = "other"; - } - case "shutdown" -> { - _classname = "org.exist.jetty.ServerShutdown"; - _mode = "other"; - } - case null, default -> { - _classname = args[0]; - _mode = "other"; - } - } - - final String[] nargs = new String[args.length - 1]; - if (args.length > 1) { - System.arraycopy(args, 1, nargs, 0, args.length - 1); - } - args = nargs; + _classname = getClassName(args); } else { _classname = "org.exist.launcher.LauncherWrapper"; - _mode = "other"; + setMode(MODE_OTHER); } - if (_debug) { - System.err.println("mode=" + _mode); + if (inDebugMode) { + System.err.println("mode=" + getMode()); } + // copy all elements except 1st one + final String[] newArguments = new String[args.length - 1]; + if (args.length > 1) { + System.arraycopy(args, 1, newArguments, 0, args.length - 1); + } + args = newArguments; + // try and figure out exist home dir final Optional existHomeDir = getFromSysPropOrEnv(PROP_EXIST_HOME, ENV_EXIST_HOME).map(Paths::get); // try to find Jetty - if ("jetty".equals(_mode) || "standalone".equals(_mode)) { - final Optional jettyHomeDir = getFromSysPropOrEnv(PROP_JETTY_HOME, ENV_JETTY_HOME).map(Paths::get); - - Optional existJettyConfigFile = getFromSysPropOrEnv(PROP_EXIST_JETTY_CONFIG, ENV_EXIST_JETTY_CONFIG).map(Paths::get); - if (existJettyConfigFile.isEmpty()) { - final String config; - if ("jetty".equals(_mode)) { - config = STANDARD_ENABLED_JETTY_CONFIGS; - } else { - config = STANDALONE_ENABLED_JETTY_CONFIGS; - } - - if (jettyHomeDir.isPresent() && Files.exists(jettyHomeDir.get().resolve(CONFIG_DIR_NAME))) { - existJettyConfigFile = jettyHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve(config)); - } - - if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(CONFIG_DIR_NAME))) { - existJettyConfigFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve(config)); - } - - if (existJettyConfigFile.isEmpty()) { - System.err.println("ERROR: jetty config file could not be found! Make sure to set exist.jetty.config or EXIST_JETTY_CONFIG."); - System.err.flush(); - throw new StartException(ERROR_CODE_NO_JETTY_CONFIG); - } - } - final String[] jettyStartArgs = new String[1 + args.length]; - jettyStartArgs[0] = existJettyConfigFile.get().toAbsolutePath().toString(); - System.arraycopy(args, 0, jettyStartArgs, 1, args.length); - args = jettyStartArgs; + if (MODE_JETTY.equals(getMode()) || MODE_STANDALONE.equals(getMode())) { + args = configureJetty(args, existHomeDir); } - // find log4j2.xml - Optional log4jConfigurationFile = Optional.ofNullable(System.getProperty(PROP_LOG4J_CONFIGURATION_FILE)).map(Paths::get); - if (log4jConfigurationFile.isEmpty()) { - if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(CONFIG_DIR_NAME))) { - log4jConfigurationFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve("log4j2.xml")); - } + setupLog4j2(existHomeDir); - if (log4jConfigurationFile.isPresent() && Files.isReadable(log4jConfigurationFile.get())) { - System.setProperty(PROP_LOG4J_CONFIGURATION_FILE, log4jConfigurationFile.get().toAbsolutePath().toString()); - } - } + // Modify behavior XML resolver for > 5.x [2024] + System.setProperty(PROP_XML_CATALOG_ALWAYS_RESOLVE, "false"); - if (log4jConfigurationFile.isPresent()) { - //redirect JUL to log4j2 unless otherwise specified - System.setProperty(PROP_JUL_MANAGER, - Optional.ofNullable(System.getProperty(PROP_JUL_MANAGER)).orElse("org.apache.logging.log4j.jul.LogManager")); - } + // Clean up tempdir for Jetty... + tweakTempDirectory(); - // Enable JXM support log4j since v2.24.0 [2024] - System.setProperty(PROP_LOG4J_DISABLEJMX, "false"); + // Setup classloader + final EXistClassLoader eXistClassLoader = getEXistClassLoader(); - // Modify behavior XML resolver for > 5.x [2024] - System.setProperty(PROP_XML_CATALOG_ALWAYS_RESOLVE, "false"); + // Invoke main class using new classloader. + try { + invokeMain(eXistClassLoader, _classname, args); + } catch (final Exception e) { + e.printStackTrace(); + throw new StartException(ERROR_CODE_GENERAL); + } + } + + private String getClassName(final String[] args) { + final String className; + switch (args[0]) { + case MODE_CLIENT -> { + className = "org.exist.client.InteractiveClient"; + setMode(MODE_CLIENT); + } + case MODE_BACKUP -> { + className = "org.exist.backup.Main"; + setMode(MODE_BACKUP); + } + case MODE_JETTY, MODE_STANDALONE -> { + className = "org.exist.jetty.JettyStart"; + setMode(args[0]); + } + case "launch" -> { + className = "org.exist.launcher.LauncherWrapper"; + setMode(MODE_JETTY); + } + case "launcher" -> { + className = "org.exist.launcher.LauncherWrapper"; + setMode(MODE_OTHER); + } + case "shutdown" -> { + className = "org.exist.jetty.ServerShutdown"; + setMode(MODE_OTHER); + } + case null, default -> { + className = args[0]; + setMode(MODE_OTHER); + } + } + return className; + } - // clean up tempdir for Jetty... + private void tweakTempDirectory() { try { final Path tmpdir = Paths.get(System.getProperty(PROP_JAVA_TEMP_DIR)).toAbsolutePath(); if (Files.isDirectory(tmpdir)) { @@ -282,22 +283,42 @@ public void runEx(String[] args) throws StartException { // ignore } - if (_debug) { + if (inDebugMode) { System.err.println(PROP_JAVA_TEMP_DIR + "=" + System.getProperty(PROP_JAVA_TEMP_DIR)); } + } - // setup classloader - final Classpath _classpath = new Classpath(); - final EXistClassLoader cl = _classpath.getClassLoader(null); - Thread.currentThread().setContextClassLoader(cl); + private String[] configureJetty(final String[] arguments, final Optional existHomeDir) throws StartException { + final Optional jettyHomeDir = getFromSysPropOrEnv(PROP_JETTY_HOME, ENV_JETTY_HOME).map(Paths::get); - // Invoke main class using new classloader. - try { - invokeMain(cl, _classname, args); - } catch (final Exception e) { - e.printStackTrace(); - throw new StartException(ERROR_CODE_GENERAL); + Optional existJettyConfigFile = getFromSysPropOrEnv(PROP_EXIST_JETTY_CONFIG, ENV_EXIST_JETTY_CONFIG).map(Paths::get); + if (existJettyConfigFile.isEmpty()) { + final String config; + if (MODE_JETTY.equals(_mode)) { + config = STANDARD_ENABLED_JETTY_CONFIGS; + } else { + config = STANDALONE_ENABLED_JETTY_CONFIGS; + } + + if (jettyHomeDir.isPresent() && Files.exists(jettyHomeDir.get().resolve(CONFIG_DIR_NAME))) { + existJettyConfigFile = jettyHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve(config)); + } + + if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(CONFIG_DIR_NAME))) { + existJettyConfigFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve(config)); + } + + if (existJettyConfigFile.isEmpty()) { + System.err.println("ERROR: jetty config file could not be found! Make sure to set exist.jetty.config or EXIST_JETTY_CONFIG."); + System.err.flush(); + throw new StartException(ERROR_CODE_NO_JETTY_CONFIG); + } } + + final String[] jettyStartArgs = new String[1 + arguments.length]; + jettyStartArgs[0] = existJettyConfigFile.get().toAbsolutePath().toString(); + System.arraycopy(arguments, 0, jettyStartArgs, 1, arguments.length); + return jettyStartArgs; } private Optional getFromSysPropOrEnv(final String sysPropName, final String envVarName) { @@ -308,29 +329,21 @@ private Optional getFromSysPropOrEnv(final String sysPropName, final Str value.ifPresent(s -> System.setProperty(sysPropName, s)); } - if (_debug && value.isPresent()) { + if (inDebugMode && value.isPresent()) { System.err.println(sysPropName + "=" + System.getProperty(sysPropName)); } return value; } - public void shutdown() { - // only used in test suite - try { - shutdownEx(); - } catch (final StopException e) { - e.printStackTrace(); - } - } - - public void shutdownEx() throws StopException { + public void shutdownExistdb() throws StopException { // 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) { throw new StopException(e.getMessage(), e); diff --git a/exist-start/src/main/java/org/exist/start/classloader/Classpath.java b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java index 98ce270ed8d..a3dcbec6131 100644 --- a/exist-start/src/main/java/org/exist/start/classloader/Classpath.java +++ b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java @@ -55,7 +55,7 @@ */ public class Classpath implements Iterable { - final List _elements = new ArrayList<>(); + private final List classPathElements = new ArrayList<>(); public Classpath() { } @@ -92,8 +92,8 @@ public boolean addComponent(final String component) { public boolean addComponent(final Path path) throws InvalidPathException { if (path != null & Files.exists(path)) { final Path key = path.toAbsolutePath(); - if (!_elements.contains(key)) { - _elements.add(key); + if (!classPathElements.contains(key)) { + classPathElements.add(key); return true; } } @@ -112,19 +112,19 @@ public void addClasspath(final String s) { @Override public String toString() { final StringBuilder cp = new StringBuilder(1024); - final int cnt = _elements.size(); - if (cnt >= 1) { - cp.append(_elements.getFirst()); + final int size = classPathElements.size(); + if (size >= 1) { + cp.append(classPathElements.getFirst()); } - for (int i = 1; i < cnt; i++) { + for (int i = 1; i < size; i++) { cp.append(File.pathSeparatorChar); - cp.append(_elements.get(i)); + cp.append(classPathElements.get(i)); } return cp.toString(); } public EXistClassLoader getClassLoader(ClassLoader parent) { - final URL[] urls = _elements.stream().map(Path::toUri).map(u -> { + final URL[] urls = classPathElements.stream().map(Path::toUri).map(u -> { try { return Optional.of(u.toURL()); } catch (final MalformedURLException e) { @@ -136,7 +136,8 @@ public EXistClassLoader getClassLoader(ClassLoader parent) { parent = or( or( or( - Optional.ofNullable(parent), () -> Optional.ofNullable(Thread.currentThread().getContextClassLoader()) + Optional.ofNullable(parent), + () -> Optional.ofNullable(Thread.currentThread().getContextClassLoader()) ), () -> Optional.ofNullable(Classpath.class.getClassLoader()) ), @@ -148,6 +149,6 @@ public EXistClassLoader getClassLoader(ClassLoader parent) { @Override public Iterator iterator() { - return _elements.iterator(); + return classPathElements.iterator(); } } From 4b7d14d45c0e16428a1ad4ee973818168161b97c Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Mon, 14 Apr 2025 14:13:49 +0200 Subject: [PATCH 12/20] [bugfix] Fixed another javadoc Only reported by Linux build .... --- .../main/java/org/exist/start/CompatibleJavaVersionCheck.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index 4be3f5c2f8b..04345a8c435 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -42,7 +42,7 @@ * OpenJDK versions 12 through 15.0.1 suffer from a critical bug in the JIT C2 compiler that will * cause data loss in eXist-db. The problem has been reported to the OpenJDK community. *

- * For moreC2: Masked byte comparisons with large masks produce wrong result on x86 * - eXist-db does not run correctly on JDKs 12, 13, 14 and 15 #3375 * From 03ebe3508057ac2ad429bf6b1add0664ee648624 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Mon, 14 Apr 2025 20:54:14 +0200 Subject: [PATCH 13/20] [refactor] Update licence check, extend javadoc --- exist-start/pom.xml | 5 +-- .../src/main/java/org/exist/start/Main.java | 36 +++++-------------- 2 files changed, 10 insertions(+), 31 deletions(-) diff --git a/exist-start/pom.xml b/exist-start/pom.xml index 34ff4692b3b..cee8aa18b4f 100644 --- a/exist-start/pom.xml +++ b/exist-start/pom.xml @@ -77,7 +77,6 @@ Mortbay-APACHE-2-license.template.txt src/main/java/org/exist/start/classloader/Classpath.java - src/main/java/org/exist/start/Main.java @@ -94,9 +93,7 @@ The original license statement is also included below.]]> - src/main/java/org/exist/start/Classpath.java - src/main/java/org/exist/start/Main.java - src/main/java/org/exist/start/Version.java + src/main/java/org/exist/start/classloader/Classpath.java diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 951609f4795..80b8936b69f 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -1,7 +1,4 @@ /* - * NOTE: This file is in part based on code from Mort Bay Consulting. - * The original license statement is also included below. - * * eXist-db Open Source Native XML Database * Copyright (C) 2001 The eXist-db Authors * @@ -21,20 +18,6 @@ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * --------------------------------------------------------------------- - * - * Copyright 2002-2005 Mort Bay Consulting Pty. Ltd. - * - * Licensed 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.exist.start; @@ -50,16 +33,15 @@ import java.util.Optional; /** - * This is an adopted version of the corresponding classes shipped - * with Jetty. Modified for eXist-db! - * - * @author Jan Hlavaty (hlavac@code.cz) - * @author Wolfgang Meier (meier@ifs.tu-darmstadt.de) - * @version $Revision$ - * TODO: - * - finish possible jetty.home locations - * - better handling of errors (i.e. when jetty.home cannot be autodetected...) - * - include entries from lib _when needed_ + * eXist-db bootstrap start helper. + *

+ * The purpose of the class is to setup and configure the (java) environment + * before the database is actually started, by invoking other classes. + *

+ * The class is designed to have only dependencies with java.* packages. + *

+ * The original class was based on Jetty startup code (Mort Bay Consulting, Apache licenced) + * but has completely revised and reimplemented over time. */ public class Main { From c3ad804586c8de44432ad20e295248e7e1e426e4 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Mon, 14 Apr 2025 20:55:19 +0200 Subject: [PATCH 14/20] [bugfix] address code quality issues --- .../start/CompatibleJavaVersionCheck.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index 04345a8c435..09e2ba339c3 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -24,7 +24,7 @@ import java.util.Optional; import java.util.stream.Stream; -import static org.exist.start.CompatibleJavaVersionCheck.IncompatibleJavaVersion.IncompatibleJavaVersion; +import static org.exist.start.CompatibleJavaVersionCheck.IncompatibleJavaVersion.create; import static org.exist.start.Main.ERROR_CODE_INCOMPATIBLE_JAVA_DETECTED; /** @@ -51,10 +51,10 @@ public class CompatibleJavaVersionCheck { private static final IncompatibleJavaVersion[] INCOMPATIBLE_JAVA_VERSIONS = { - IncompatibleJavaVersion(12), - IncompatibleJavaVersion(13), - IncompatibleJavaVersion(14), - IncompatibleJavaVersion(15, 0, 2), + create(12), + create(13), + create(14), + IncompatibleJavaVersion.create(15, 0, 2), }; private static final String INCOMPATIBLE_JAVA_VERSION_NOTICE = @@ -152,16 +152,16 @@ private IncompatibleJavaVersion(final int major, /* @Nullable */ final Integer l this.lessThanPatch = lessThanPatch; } - public static IncompatibleJavaVersion IncompatibleJavaVersion(final int major, /* @Nullable */ final Integer lessThanMinor, /* @Nullable */ final Integer lessThanPatch) { + public static IncompatibleJavaVersion create(final int major, /* @Nullable */ final Integer lessThanMinor, /* @Nullable */ final Integer lessThanPatch) { return new IncompatibleJavaVersion(major, lessThanMinor, lessThanPatch); } - public static IncompatibleJavaVersion IncompatibleJavaVersion(final int major, /* @Nullable */ final Integer lessThanMinor) { - return IncompatibleJavaVersion(major, lessThanMinor, null); + public static IncompatibleJavaVersion create(final int major, /* @Nullable */ final Integer lessThanMinor) { + return new IncompatibleJavaVersion(major, lessThanMinor, null); } - public static IncompatibleJavaVersion IncompatibleJavaVersion(final int major) { - return IncompatibleJavaVersion(major, null, null); + public static IncompatibleJavaVersion create(final int major) { + return new IncompatibleJavaVersion(major, null, null); } } } From 01fba96bf4d236461c0bd41779ddd997f41b7ab1 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Mon, 14 Apr 2025 21:15:06 +0200 Subject: [PATCH 15/20] [refactor] Address code review feedback --- .../src/main/java/org/exist/start/Main.java | 2 +- .../exist/start/classloader/Classpath.java | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 80b8936b69f..64277325847 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -276,7 +276,7 @@ private String[] configureJetty(final String[] arguments, final Optional e Optional existJettyConfigFile = getFromSysPropOrEnv(PROP_EXIST_JETTY_CONFIG, ENV_EXIST_JETTY_CONFIG).map(Paths::get); if (existJettyConfigFile.isEmpty()) { final String config; - if (MODE_JETTY.equals(_mode)) { + if (MODE_JETTY.equals(getMode())) { config = STANDARD_ENABLED_JETTY_CONFIGS; } else { config = STANDALONE_ENABLED_JETTY_CONFIGS; diff --git a/exist-start/src/main/java/org/exist/start/classloader/Classpath.java b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java index a3dcbec6131..bd3444f99bc 100644 --- a/exist-start/src/main/java/org/exist/start/classloader/Classpath.java +++ b/exist-start/src/main/java/org/exist/start/classloader/Classpath.java @@ -124,25 +124,31 @@ public String toString() { } public EXistClassLoader getClassLoader(ClassLoader parent) { - final URL[] urls = classPathElements.stream().map(Path::toUri).map(u -> { - try { - return Optional.of(u.toURL()); - } catch (final MalformedURLException e) { - return Optional.empty(); - } - }).filter(Optional::isPresent).map(Optional::get).toArray(URL[]::new); + final URL[] urls = classPathElements.stream() + .map(Path::toUri).map(u -> + { + try { + return Optional.of(u.toURL()); + } catch (final MalformedURLException e) { + return Optional.empty(); + } + }) + .filter(Optional::isPresent) + .map(Optional::get) + .toArray(URL[]::new); // try and ensure we have a classloader parent = or( or( - or( - Optional.ofNullable(parent), - () -> Optional.ofNullable(Thread.currentThread().getContextClassLoader()) - ), - () -> Optional.ofNullable(Classpath.class.getClassLoader()) + or( + Optional.ofNullable(parent), + () -> Optional.ofNullable(Thread.currentThread().getContextClassLoader()) + ), + () -> Optional.ofNullable(Classpath.class.getClassLoader()) ), () -> Optional.ofNullable(ClassLoader.getSystemClassLoader()) - ).orElse(null); + ) + .orElse(null); return new EXistClassLoader(urls, parent); } From 69ccd6cb7a3eb59ca5c2fedeb927826f83ec95cb Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Tue, 15 Apr 2025 14:16:34 +0200 Subject: [PATCH 16/20] [refactor] update javadoc --- exist-start/src/main/java/org/exist/start/Main.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 64277325847..4dd33e70f64 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -34,12 +34,12 @@ /** * eXist-db bootstrap start helper. - *

+ *

* The purpose of the class is to setup and configure the (java) environment * before the database is actually started, by invoking other classes. - *

+ *

* The class is designed to have only dependencies with java.* packages. - *

+ *

* The original class was based on Jetty startup code (Mort Bay Consulting, Apache licenced) * but has completely revised and reimplemented over time. */ From a7a199261cf65bf7894f0cd7b97dad8581db075c Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Tue, 15 Apr 2025 21:44:37 +0200 Subject: [PATCH 17/20] [refactor] Revise classes more, added javadoc --- .../java/org/exist/service/ExistDbDaemon.java | 2 +- .../src/main/java/org/exist/start/Main.java | 290 ++++++++++++------ .../start/classloader/ReflectionUtils.java | 75 +++++ 3 files changed, 276 insertions(+), 91 deletions(-) create mode 100644 exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java diff --git a/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java b/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java index 394fd5255e6..48ae21efbcf 100644 --- a/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java +++ b/exist-service/src/main/java/org/exist/service/ExistDbDaemon.java @@ -36,7 +36,7 @@ public class ExistDbDaemon implements Daemon { private String[] args = null; private void init(final String args[]) { - this.main = new Main("jetty"); + this.main = new Main(Main.MODE_JETTY); this.args = args; } diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 4dd33e70f64..4802dc1a900 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -21,8 +21,8 @@ */ package org.exist.start; -import org.exist.start.classloader.Classpath; import org.exist.start.classloader.EXistClassLoader; +import org.exist.start.classloader.ReflectionUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -70,7 +70,7 @@ public class Main { private static final String PROP_LOG4J_CONFIGURATION_FILE = "log4j.configurationFile"; private static Main exist; private final boolean inDebugMode = Boolean.getBoolean(PROP_EXIST_START_DEBUG); - private String _mode = MODE_JETTY; + private String _mode = MODE_OTHER; private Main() { } @@ -101,46 +101,43 @@ public static Main getMain() { return exist; } - private 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); - } - - private static EXistClassLoader getEXistClassLoader() { - final Classpath _classpath = new Classpath(); - final EXistClassLoader eXistClassLoader = _classpath.getClassLoader(null); - Thread.currentThread().setContextClassLoader(eXistClassLoader); - return eXistClassLoader; - } - + /** + * Configures the Log4j2 logging framework by establishing the appropriate logging configuration file + * and enabling necessary properties to ensure proper logging behavior. + *

+ * This method searches for a Log4j2 configuration file in multiple locations: + * 1. Directly through a system property. + * 2. In a specified home directory, if available. + *

+ * It also configures additional system properties, such as setting up a JUL bridge for Log4j2 + * and enabling JMX support. + * + * @param existHomeDir an optional path to the eXist-db home directory where the Log4j2 configuration + * file may be located + */ private static void setupLog4j2(final Optional existHomeDir) { - // find log4j2.xml + + // Get path from system property Optional log4jConfigurationFile = Optional.ofNullable(System.getProperty(PROP_LOG4J_CONFIGURATION_FILE)).map(Paths::get); + + // Try to find configuration file is not already found. if (log4jConfigurationFile.isEmpty()) { + + // Try to find file in eXist-db directory. if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(CONFIG_DIR_NAME))) { log4jConfigurationFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve("log4j2.xml")); } + // If file was found, update system property. if (log4jConfigurationFile.isPresent() && Files.isReadable(log4jConfigurationFile.get())) { System.setProperty(PROP_LOG4J_CONFIGURATION_FILE, log4jConfigurationFile.get().toAbsolutePath().toString()); } } if (log4jConfigurationFile.isPresent()) { - //redirect JUL to log4j2 unless otherwise specified - System.setProperty(PROP_JUL_MANAGER, Optional - .ofNullable(System.getProperty(PROP_JUL_MANAGER)) - .orElse("org.apache.logging.log4j.jul.LogManager")); + // redirect JUL to log4j2 unless otherwise specified + System.setProperty(PROP_JUL_MANAGER, + Optional.ofNullable(System.getProperty(PROP_JUL_MANAGER)).orElse("org.apache.logging.log4j.jul.LogManager")); } // Enable JMX support log4j since v2.24.0 [2024] @@ -166,37 +163,37 @@ public void run(final String[] args) { } } + /** + * Starts the eXist-db application by initializing required configurations, verifying + * Java compatibility, setting up logging, and configuring the appropriate runtime environment + * based on specified modes such as Jetty or standalone. + * This method resolves relevant arguments and invokes the main class using a custom + * class loader for bootstrapping the application. + * + * @param args an array of strings representing the command-line arguments; the first + * argument determines the mode or main class to be invoked, followed by + * additional configuration-specific arguments. It may be modified internally + * during the execution to adjust for specific configurations. + * @throws StartException if any error occurs during the startup process such as + * incompatible Java version, misconfiguration, or failure to + * resolve required resources. + */ public void startExistdb(String[] args) throws StartException { // Check if the OpenJDK version can corrupt eXist-db CompatibleJavaVersionCheck.checkForCompatibleJavaVersion(); - final String _classname; - if (args.length > 0) { - _classname = getClassName(args); + final String mainClassName = getClassName(args); - } else { - _classname = "org.exist.launcher.LauncherWrapper"; - setMode(MODE_OTHER); - } - - if (inDebugMode) { - System.err.println("mode=" + getMode()); - } - - // copy all elements except 1st one - final String[] newArguments = new String[args.length - 1]; - if (args.length > 1) { - System.arraycopy(args, 1, newArguments, 0, args.length - 1); - } - args = newArguments; + args = stripFirstElement(args); // try and figure out exist home dir final Optional existHomeDir = getFromSysPropOrEnv(PROP_EXIST_HOME, ENV_EXIST_HOME).map(Paths::get); // try to find Jetty if (MODE_JETTY.equals(getMode()) || MODE_STANDALONE.equals(getMode())) { - args = configureJetty(args, existHomeDir); + final Path jettyConfig = configureForJetty(existHomeDir); + args = addFirstElement(args, jettyConfig); } setupLog4j2(existHomeDir); @@ -208,52 +205,117 @@ public void startExistdb(String[] args) throws StartException { tweakTempDirectory(); // Setup classloader - final EXistClassLoader eXistClassLoader = getEXistClassLoader(); + final EXistClassLoader eXistClassLoader = ReflectionUtils.getEXistClassLoader(); // Invoke main class using new classloader. try { - invokeMain(eXistClassLoader, _classname, args); + ReflectionUtils.invokeMain(eXistClassLoader, mainClassName, args); } catch (final Exception e) { e.printStackTrace(); throw new StartException(ERROR_CODE_GENERAL); } } + /** + * Adds a specified element, represented by the absolute path of the provided `jettyConfig`, + * as the first element in a new array and appends all elements of the input array to it. + * This method creates a new array combining the provided `jettyConfig` as the first argument + * followed by the elements of the given `args` array. + * + * @param args an array of strings representing the original arguments; must not be null + * @param jettyConfig the `Path` object whose absolute path is added as the first element + * in the new array; must not be null + * @return a new array of strings where the first element is the absolute path of the + * given `jettyConfig` and all subsequent elements are from the provided `args` array + */ + private static String[] addFirstElement(final String[] args, final Path jettyConfig) { + final String[] jettyStartArgs = new String[1 + args.length]; + jettyStartArgs[0] = jettyConfig.toAbsolutePath().toString(); + System.arraycopy(args, 0, jettyStartArgs, 1, args.length); + return jettyStartArgs; + } + + /** + * Removes the first element from the provided array of strings and returns the resulting array. + * If the input array has one or no elements, an empty array is returned. + * + * @param args an array of strings from which the first element is to be removed; + * must not be null, but can be empty + * @return a new array containing all elements of the input array except the first one; + * if the input array contains no elements or only one element, an empty array is returned + */ + private static String[] stripFirstElement(final String[] args) { + final String[] newArguments = new String[args.length - 1]; + if (args.length > 1) { + System.arraycopy(args, 1, newArguments, 0, args.length - 1); + } + return newArguments; + } + + /** + * Determines and returns the class name to be used based on the provided arguments. + * The method evaluates the first argument in the provided array and maps it to a + * corresponding class name. If no arguments are provided or the arguments are invalid, + * a default class name is returned. + * + * @param args an array of strings representing command-line arguments; can be null or empty + * @return the fully qualified name of the class as a string + */ private String getClassName(final String[] args) { + final String className; - switch (args[0]) { - case MODE_CLIENT -> { - className = "org.exist.client.InteractiveClient"; - setMode(MODE_CLIENT); - } - case MODE_BACKUP -> { - className = "org.exist.backup.Main"; - setMode(MODE_BACKUP); - } - case MODE_JETTY, MODE_STANDALONE -> { - className = "org.exist.jetty.JettyStart"; - setMode(args[0]); - } - case "launch" -> { - className = "org.exist.launcher.LauncherWrapper"; - setMode(MODE_JETTY); - } - case "launcher" -> { - className = "org.exist.launcher.LauncherWrapper"; - setMode(MODE_OTHER); - } - case "shutdown" -> { - className = "org.exist.jetty.ServerShutdown"; - setMode(MODE_OTHER); - } - case null, default -> { - className = args[0]; - setMode(MODE_OTHER); + + if (args == null || args.length == 0) { + className = "org.exist.launcher.LauncherWrapper"; + setMode(MODE_OTHER); + + } else { + final String firstArgument = args[0]; + switch (firstArgument) { + case MODE_CLIENT -> { + className = "org.exist.client.InteractiveClient"; + setMode(firstArgument); + } + case MODE_BACKUP -> { + className = "org.exist.backup.Main"; + setMode(firstArgument); + } + case MODE_JETTY, MODE_STANDALONE -> { + className = "org.exist.jetty.JettyStart"; + setMode(firstArgument); + } + case "launch" -> { + className = "org.exist.launcher.LauncherWrapper"; + setMode(MODE_JETTY); + } + case "launcher" -> className = "org.exist.launcher.LauncherWrapper"; + case "shutdown" -> className = "org.exist.jetty.ServerShutdown"; + case null, default -> className = firstArgument; } } + + if (inDebugMode) { + System.err.println("mode=" + getMode()); + } + return className; } + /** + * Adjusts the value of the system property for the temporary directory used by the Java application. + * This method ensures the value of the temporary directory system property is set to an absolute + * path if the directory exists, and logs the value if the application is in debug mode. + *

+ * Behavior: + * - Retrieves the system property for the Java temporary directory. + * - Converts the property value into an absolute path and verifies if it points to an existing directory. + * - If the directory exists, the property is updated with the absolute path. + * - Logs the value of the temporary directory system property to the error stream if debugging is enabled. + *

+ * Exceptions: + * - Catches and silently ignores {@link InvalidPathException} if the system property contains + * an invalid path or cannot be resolved as a valid directory. + */ private void tweakTempDirectory() { try { final Path tmpdir = Paths.get(System.getProperty(PROP_JAVA_TEMP_DIR)).toAbsolutePath(); @@ -270,26 +332,45 @@ private void tweakTempDirectory() { } } - private String[] configureJetty(final String[] arguments, final Optional existHomeDir) throws StartException { - final Optional jettyHomeDir = getFromSysPropOrEnv(PROP_JETTY_HOME, ENV_JETTY_HOME).map(Paths::get); + /** + * Configures the system to use the appropriate Jetty configuration file based on the provided + * eXist-db home directory and other system parameters. The method attempts to resolve the + * configuration file either from system properties, environment variables, or defaults to a + * pre-configured standard or standalone setup. If the configuration cannot be located, a + * {@code StartException} is thrown. + * + * @param existHomeDir an optional path to the eXist-db home directory where the configuration + * may be located + * @return the path to the resolved Jetty configuration file + * @throws StartException if no valid configuration file can be located + */ + private Path configureForJetty(final Optional existHomeDir) throws StartException { + // Get configured path for Jetty config file Optional existJettyConfigFile = getFromSysPropOrEnv(PROP_EXIST_JETTY_CONFIG, ENV_EXIST_JETTY_CONFIG).map(Paths::get); + + // If configuration was not found if (existJettyConfigFile.isEmpty()) { - final String config; - if (MODE_JETTY.equals(getMode())) { - config = STANDARD_ENABLED_JETTY_CONFIGS; - } else { - config = STANDALONE_ENABLED_JETTY_CONFIGS; - } + // Detect 'Normal Jetty" or "Standalone" modus. + final String config = MODE_JETTY.equals(getMode()) + ? STANDARD_ENABLED_JETTY_CONFIGS + : STANDALONE_ENABLED_JETTY_CONFIGS; + + // Get path for jetty homedir + final Optional jettyHomeDir = getFromSysPropOrEnv(PROP_JETTY_HOME, ENV_JETTY_HOME).map(Paths::get); + + // Load configuration from Jetty directory if (jettyHomeDir.isPresent() && Files.exists(jettyHomeDir.get().resolve(CONFIG_DIR_NAME))) { existJettyConfigFile = jettyHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve(config)); } + // Load configuration from eXist-db directory if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(CONFIG_DIR_NAME))) { existJettyConfigFile = existHomeDir.map(f -> f.resolve(CONFIG_DIR_NAME).resolve(config)); } + // Verify that jetty configuration could be found if (existJettyConfigFile.isEmpty()) { System.err.println("ERROR: jetty config file could not be found! Make sure to set exist.jetty.config or EXIST_JETTY_CONFIG."); System.err.flush(); @@ -297,20 +378,31 @@ private String[] configureJetty(final String[] arguments, final Optional e } } - final String[] jettyStartArgs = new String[1 + arguments.length]; - jettyStartArgs[0] = existJettyConfigFile.get().toAbsolutePath().toString(); - System.arraycopy(arguments, 0, jettyStartArgs, 1, arguments.length); - return jettyStartArgs; + return existJettyConfigFile.get(); } + /** + * Retrieves a value for a given configuration key either from a system property or an environment variable. + * If the value is found in the environment variable and not in the system property, the method sets the system + * property with the retrieved value. + * + * @param sysPropName the name of the system property to look up + * @param envVarName the name of the environment variable to look up + * @return an {@code Optional} containing the retrieved value if found, + * or an empty {@code Optional} if no value is available + */ private Optional getFromSysPropOrEnv(final String sysPropName, final String envVarName) { Optional value = Optional.ofNullable(System.getProperty(sysPropName)); + + // Not found in system property, try environment if (value.isEmpty()) { value = Optional.ofNullable(System.getenv().get(envVarName)); + // if we managed to detect from environment, store it in a system property value.ifPresent(s -> System.setProperty(sysPropName, s)); } + // Report out if (inDebugMode && value.isPresent()) { System.err.println(sysPropName + "=" + System.getProperty(sysPropName)); } @@ -318,6 +410,24 @@ private Optional getFromSysPropOrEnv(final String sysPropName, final Str return value; } + /** + * Shuts down the eXist-db database by invoking internal methods to stop + * all broker pools associated with the database. + *

+ * This method utilizes reflection to access and call the internal `stopAll` + * method of the `org.exist.storage.BrokerPools` class. It ensures a clean + * shutdown by disabling certain features while stopping the broker pools. + *

+ * Exceptions: + * - Throws {@link StopException} if any error occurs during the reflective + * invocation, such as the target class or method being unavailable, + * access restrictions, or runtime invocation issues. + *

+ * Note: + * This method is primarily intended for use in test environments. + * + * @throws StopException if the shutdown process encounters an error + */ public void shutdownExistdb() throws StopException { // only used in test suite try { diff --git a/exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java b/exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java new file mode 100644 index 00000000000..692adaa692a --- /dev/null +++ b/exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java @@ -0,0 +1,75 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.start.classloader; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ReflectionUtils { + /** + * Invokes the main method of a specified class using the provided class loader and arguments. + *

+ * This method dynamically loads a class by its name, retrieves its {@code main} method, + * and then executes it with the given arguments. It is commonly used to launch a Java + * application from another Java program. + * + * @param classloader the {@link ClassLoader} to use for loading the class + * @param classname the fully qualified name of the class containing the main method + * @param args an array of {@code String} arguments to pass to the main method + * @throws IllegalAccessException if the main method is inaccessible + * @throws InvocationTargetException if an error occurs during the invocation of the main method + * @throws NoSuchMethodException if the main method cannot be found in the specified class + * @throws ClassNotFoundException if the specified class cannot be located using the given class loader + */ + 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); + } + + /** + * Obtains an instance of {@link EXistClassLoader}, sets it as the context class loader + * of the current thread, and returns it. + *

+ * This method creates a new instance of the {@link Classpath} class, retrieves an + * {@link EXistClassLoader} using the classpath, and assigns it as the thread's context + * class loader to ensure proper class loading behavior. + * + * @return an initialized {@link EXistClassLoader} instance that becomes the context + * class loader of the current thread + */ + public static EXistClassLoader getEXistClassLoader() { + final Classpath _classpath = new Classpath(); + final EXistClassLoader eXistClassLoader = _classpath.getClassLoader(null); + Thread.currentThread().setContextClassLoader(eXistClassLoader); + return eXistClassLoader; + } +} From 482219974b985f8c5cd7011d3a0c581cda76e8d2 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Sat, 21 Jun 2025 21:09:51 +0200 Subject: [PATCH 18/20] [refactor] Revise classes more, added javadoc --- .../src/main/java/org/exist/start/Main.java | 8 +++--- .../start/classloader/ReflectionUtils.java | 26 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/Main.java b/exist-start/src/main/java/org/exist/start/Main.java index 4802dc1a900..0d49d6efdd3 100644 --- a/exist-start/src/main/java/org/exist/start/Main.java +++ b/exist-start/src/main/java/org/exist/start/Main.java @@ -70,13 +70,13 @@ public class Main { private static final String PROP_LOG4J_CONFIGURATION_FILE = "log4j.configurationFile"; private static Main exist; private final boolean inDebugMode = Boolean.getBoolean(PROP_EXIST_START_DEBUG); - private String _mode = MODE_OTHER; + private String mode = MODE_OTHER; private Main() { } public Main(final String mode) { - this._mode = mode; + this.mode = mode; } public static void main(final String[] args) { @@ -145,11 +145,11 @@ private static void setupLog4j2(final Optional existHomeDir) { } private String getMode() { - return _mode; + return mode; } private void setMode(final String mode) { - _mode = mode; + this.mode = mode; } public void run(final String[] args) { diff --git a/exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java b/exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java index 692adaa692a..a6f1b00b8dc 100644 --- a/exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java +++ b/exist-start/src/main/java/org/exist/start/classloader/ReflectionUtils.java @@ -25,6 +25,14 @@ import java.lang.reflect.Method; public class ReflectionUtils { + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private ReflectionUtils() { + // No instantiation allowed + } + /** * Invokes the main method of a specified class using the provided class loader and arguments. *

@@ -43,16 +51,16 @@ public class ReflectionUtils { 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 invokedClass = classloader.loadClass(classname); - final Class[] method_param_types = new Class[1]; - method_param_types[0] = args.getClass(); + final Class[] methodParamTypes = new Class[1]; + methodParamTypes[0] = args.getClass(); - final Method main = invoked_class.getDeclaredMethod("main", method_param_types); + final Method main = invokedClass.getDeclaredMethod("main", methodParamTypes); - final Object[] method_params = new Object[1]; - method_params[0] = args; - main.invoke(null, method_params); + final Object[] methodParams = new Object[1]; + methodParams[0] = args; + main.invoke(null, methodParams); } /** @@ -67,8 +75,8 @@ public static void invokeMain(final ClassLoader classloader, final String classn * class loader of the current thread */ public static EXistClassLoader getEXistClassLoader() { - final Classpath _classpath = new Classpath(); - final EXistClassLoader eXistClassLoader = _classpath.getClassLoader(null); + final Classpath classpath = new Classpath(); + final EXistClassLoader eXistClassLoader = classpath.getClassLoader(null); Thread.currentThread().setContextClassLoader(eXistClassLoader); return eXistClassLoader; } From 707c4ec4c5be4752f3d2a6558dc7113f8d9e87b3 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Wed, 16 Jul 2025 22:14:04 +0200 Subject: [PATCH 19/20] [refactor] Clean up file --- .../exist/webstart/LatestFileResolver.java | 72 +++++++------------ 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/exist-core/src/main/java/org/exist/webstart/LatestFileResolver.java b/exist-core/src/main/java/org/exist/webstart/LatestFileResolver.java index f5708f10c28..56e2113fcc1 100644 --- a/exist-core/src/main/java/org/exist/webstart/LatestFileResolver.java +++ b/exist-core/src/main/java/org/exist/webstart/LatestFileResolver.java @@ -21,18 +21,19 @@ */ package org.exist.webstart; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.exist.util.FileUtils; + import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; import java.util.List; -import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; + +import static org.exist.util.FileUtils.fileName; /** * This class uses regex pattern matching to find the latest version of a @@ -40,19 +41,17 @@ * * @author Ben Schmaus (exist@benschmaus.com) * @version $Revision$ - * @see LatestFileResolver#getResolvedFileName(String) + * @see org.exist.webstart.LatestFileResolver#getResolvedFileName(String) */ public class LatestFileResolver { + private static final Logger LOGGER = LogManager.getLogger(); + // Pattern that can be used to indicate that the // latest version of a particular file should be added to the classpath. // E.g., commons-fileupload-%latest%.jar would resolve to something like // commons-fileupload-1.1.jar. - private final static Pattern latestVersionPattern = Pattern.compile("(%latest%)"); - - // Set debug mode for each file resolver instance based on whether or - // not the system was started with debugging turned on. - private static final boolean _debug = Boolean.getBoolean("exist.start.debug"); + private static final Pattern LATEST_VERSION_PATTERN = Pattern.compile("(%latest%)"); /** * If the passed file name contains a %latest% token, @@ -64,15 +63,16 @@ public class LatestFileResolver { * @return Resolved filename. */ public String getResolvedFileName(final String filename) { - final Matcher matches = latestVersionPattern.matcher(filename); + final Matcher matches = LATEST_VERSION_PATTERN.matcher(filename); if (!matches.find()) { return filename; } + final String[] fileinfo = filename.split("%latest%"); // Path of file up to the beginning of the %latest% token. final String uptoToken = fileinfo[0]; - // Dir that should contain our jar. + // Directory that should contain our jar. final String containerDirName = uptoToken.substring(0, uptoToken.lastIndexOf(File.separatorChar)); final Path containerDir = Paths.get(containerDirName); @@ -83,49 +83,25 @@ public String getResolvedFileName(final String filename) { final Pattern pattern = Pattern.compile(patternString); final Matcher matcher = pattern.matcher(""); - List jars; try { - jars = list(containerDir, p -> { + final List jars = FileUtils.list(containerDir, p -> { matcher.reset(fileName(p)); return matcher.find(); }); - } catch (final IOException e) { - System.err.println("ERROR: No jars found in " + containerDir.toAbsolutePath()); - e.printStackTrace(); - jars = Collections.emptyList(); - } - if (!jars.isEmpty()) { - final String actualFileName = jars.getFirst().toAbsolutePath().toString(); - if (_debug) { - System.err.println("Found match: " + actualFileName + " for jar file pattern: " + filename); - } - return actualFileName; - } else { - if (_debug) { - System.err.println("WARN: No latest version found for JAR file: '" + filename + "'"); + if (jars.isEmpty()) { + LOGGER.warn("WARN: No latest version found for JAR file: '{}'", filename); + + } else { + final String actualFileName = jars.getFirst().toAbsolutePath().toString(); + LOGGER.debug("Found match: {} for jar file pattern: {}", actualFileName, filename); + return actualFileName; } - } - return filename; - } - /** - * 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 list(final Path directory, final Predicate filter) throws IOException { - try (final Stream entries = Files.list(directory).filter(filter)) { - return entries.collect(Collectors.toList()); + } catch (final IOException e) { + LOGGER.error("No jars found in {}. Reason: {}", containerDir.toAbsolutePath(), e.getMessage(), e); } - } - /** - * 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(); + return filename; } } From 9d548c1b20f5993a7146000f3be21414284fc601 Mon Sep 17 00:00:00 2001 From: Dannes Wessels Date: Wed, 23 Jul 2025 20:47:13 +0200 Subject: [PATCH 20/20] [refactor] Update description --- .../main/java/org/exist/start/CompatibleJavaVersionCheck.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java index 09e2ba339c3..87a45870cb2 100644 --- a/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java +++ b/exist-start/src/main/java/org/exist/start/CompatibleJavaVersionCheck.java @@ -32,8 +32,8 @@ *

* eXist-db has been compiled with Java21 (state of 2025Q2). *

- * Older versions of Java contained a number of serious compiler bugs that caused database corruptions. - * These problematic versions are deprecated and therefore this class is not relevant anymore. + * Older versions of Java contained a number of (runtime) HotSpot compiler bugs that caused database corruptions. + * These problematic versions are deprecated, and therefore, this class is not relevant anymore. *

* The code is kept for archival purposes and potential future re-usage. *