Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion bin/catalina.bat
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ if ""%1"" == ""run"" goto doRun
if ""%1"" == ""start"" goto doStart
if ""%1"" == ""stop"" goto doStop
if ""%1"" == ""configtest"" goto doConfigTest
if ""%1"" == ""config-validate"" goto doConfigValidate
if ""%1"" == ""version"" goto doVersion

echo Usage: catalina ( commands ... )
Expand All @@ -273,6 +274,7 @@ echo run Start Catalina in the current window
echo start Start Catalina in a separate window
echo stop Stop Catalina
echo configtest Run a basic syntax check on server.xml
echo config-validate Run configuration validators with detailed output
echo version What version of tomcat are you running?
goto end

Expand Down Expand Up @@ -300,7 +302,19 @@ goto execCmd

:doConfigTest
shift
set ACTION=configtest
rem Check if --validate-only argument is present
if ""%1"" == ""--validate-only"" (
set ACTION=config-validate
shift
) else (
set ACTION=configtest
)
set CATALINA_OPTS=
goto execCmd

:doConfigValidate
shift
set ACTION=config-validate
set CATALINA_OPTS=
goto execCmd

Expand Down
21 changes: 19 additions & 2 deletions bin/catalina.sh
Original file line number Diff line number Diff line change
Expand Up @@ -549,18 +549,35 @@ elif [ "$1" = "stop" ] ; then

elif [ "$1" = "configtest" ] ; then

# Check if --validate-only argument is present
if [ "$2" = "--validate-only" ] ; then
COMMAND="config-validate"
else
COMMAND="configtest"
fi

eval "\"$_RUNJAVA\"" $LOGGING_MANAGER "$JAVA_OPTS" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap configtest
org.apache.catalina.startup.Bootstrap "$COMMAND"
result=$?
if [ $result -ne 0 ]; then
if [ $result -ne 0 ] && [ "$COMMAND" = "configtest" ]; then
echo "Configuration error detected!"
fi
exit $result

elif [ "$1" = "config-validate" ] ; then

eval "\"$_RUNJAVA\"" $LOGGING_MANAGER "$JAVA_OPTS" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap config-validate
exit $?

elif [ "$1" = "version" ] ; then

eval "\"$_RUNJAVA\"" "$JAVA_OPTS" \
Expand Down
55 changes: 52 additions & 3 deletions java/org/apache/catalina/startup/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,25 @@ public void init() throws Exception {
* Load daemon.
*/
private void load(String[] arguments) throws Exception {
invokeCatalinaMethod("load", arguments);
}

/**
* Load configuration only without initializing server.
* Used for validation without binding to ports.
*/
private void loadConfigOnly(String[] arguments) throws Exception {
invokeCatalinaMethod("loadConfigOnly", arguments);
}

// Call the load() method
String methodName = "load";
/**
* Helper method to invoke a Catalina method via reflection with optional arguments.
*
* @param methodName the name of the method to invoke
* @param arguments optional arguments to pass to the method
* @throws Exception if the method invocation fails
*/
private void invokeCatalinaMethod(String methodName, String[] arguments) throws Exception {
Object[] param;
Class<?>[] paramTypes;
if (arguments == null || arguments.length == 0) {
Expand All @@ -296,7 +312,6 @@ private void load(String[] arguments) throws Exception {
method.invoke(catalinaDaemon, param);
}


/**
* getServer() for configtest
*/
Expand All @@ -307,6 +322,18 @@ private Object getServer() throws Exception {
return method.invoke(catalinaDaemon);
}

/**
* Run configuration validation tests.
*
* @return exit code (0 = success, 1 = errors found)
* @throws Exception Fatal validation error
*/
public int configtest() throws Exception {
Method method = catalinaDaemon.getClass().getMethod("configtest");
Integer exitCode = (Integer) method.invoke(catalinaDaemon);
return exitCode != null ? exitCode.intValue() : 1;
}


// ----------------------------------------------------------- Main Program

Expand Down Expand Up @@ -482,6 +509,14 @@ public static void main(String[] args) {
}
System.exit(0);
break;
case "config-validate":
daemon.loadConfigOnly(args);
if (null == daemon.getServer()) {
System.exit(1);
}
int exitCode = daemon.configtest();
System.exit(exitCode);
break;
default:
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
break;
Expand All @@ -492,12 +527,26 @@ public static void main(String[] args) {
if (throwable instanceof InvocationTargetException && throwable.getCause() != null) {
throwable = throwable.getCause();
}

if (isStartupAbort(throwable)) {
System.exit(1);
}

handleThrowable(throwable);
log.error("Error running command", throwable);
System.exit(1);
}
}

public static boolean isStartupAbort(Throwable t) {
while (t != null) {
if ("org.apache.catalina.startup.StartupAbortException".equals(t.getClass().getName())) {
return true;
}
t = t.getCause();
}
return false;
}

/**
* Obtain the name of configured home (binary) directory. Note that home and base may be the same (and are by
Expand Down
141 changes: 126 additions & 15 deletions java/org/apache/catalina/startup/Catalina.java
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,9 @@ protected boolean arguments(String[] args) {
} else if (arg.equals("configtest")) {
isGenerateCode = false;
// NOOP
} else if (arg.equals("config-validate")) {
isGenerateCode = false;
// NOOP
} else if (arg.equals("stop")) {
isGenerateCode = false;
// NOOP
Expand Down Expand Up @@ -673,17 +676,34 @@ public void stopServer(String[] arguments) {
}


/**
* Load configuration without initializing the server.
* Used for configuration validation without binding to ports.
*/
public void loadConfigOnly() {
loadInternal(false);
}

/**
* Start a new server instance.
*/
public void load() {
loadInternal(true);
}

/**
* Internal load method that handles both config-only and full initialization.
*
* @param initServer if true, initialize the server and bind to ports;
* if false, only parse configuration
*/
private void loadInternal(boolean initServer) {
if (loaded) {
return;
}
loaded = true;

long t1 = System.nanoTime();
long t1 = initServer ? System.nanoTime() : 0;

// Before digester - it may be needed
initNaming();
Expand All @@ -699,23 +719,34 @@ public void load() {
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

// Stream redirection
initStreams();
if (initServer) {
// Stream redirection
initStreams();

// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (throwOnInitFailure) {
throw new Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
Throwable t = e;
while (t.getCause() != null && t.getCause() != t) {
t = t.getCause();
}

if (t instanceof StartupAbortException) {
throw (StartupAbortException) t;
}

if (throwOnInitFailure) {
throw new Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
}

if (log.isInfoEnabled()) {
log.info(sm.getString("catalina.init",
Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
if (log.isInfoEnabled()) {
log.info(sm.getString("catalina.init",
Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
}
}

Expand All @@ -729,6 +760,23 @@ public void load(String[] args) {
if (arguments(args)) {
load();
}
} catch (Exception e) {
if (e instanceof StartupAbortException) {
throw (StartupAbortException) e;
}
e.printStackTrace(System.out);
}
}

/*
* Load configuration only using arguments
*/
public void loadConfigOnly(String[] args) {

try {
if (arguments(args)) {
loadConfigOnly();
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
Expand Down Expand Up @@ -857,6 +905,69 @@ protected void usage() {

}

/**
* Run configuration validation tests.
*
* @return exit code (0 = success, 1 = errors found)
*/
public int configtest() {
if (server == null) {
return 1;
}

try {

// Run validators
org.apache.catalina.startup.validator.ValidatorRegistry registry =
new org.apache.catalina.startup.validator.ValidatorRegistry();
org.apache.catalina.startup.validator.ValidationResult result =
registry.validate(server);

// Print results
System.out.println();
System.out.println("Configuration Validation Results");
System.out.println("=================================");
System.out.println();

if (!result.hasFindings()) {
System.out.println("No issues found. Configuration is valid.");
return 0;
}

// Group findings by severity
java.util.List<org.apache.catalina.startup.validator.ValidationResult.Finding> findings =
result.getFindings();

for (org.apache.catalina.startup.validator.ValidationResult.Finding finding : findings) {
System.out.println(finding.toString());
}

System.out.println();
System.out.println("Summary: " + result.getErrorCount() + " error(s), " +
result.getWarningCount() + " warning(s), " +
result.getInfoCount() + " info message(s)");
System.out.println();

if (result.getErrorCount() > 0) {
System.out.println("Configuration test FAILED.");
System.out.println("Configuration error detected!");
return 1;
} else {
if (result.getWarningCount() > 0) {
System.out.println("Configuration test PASSED (with warnings).");
} else {
System.out.println("Configuration test PASSED.");
}
return 0;
}

} catch (Exception e) {
log.error(sm.getString("catalina.configTestError"), e);
e.printStackTrace();
return 1;
}
}


protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
Expand Down
37 changes: 37 additions & 0 deletions java/org/apache/catalina/startup/StartupAbortException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.catalina.startup;

import java.io.Serial;

/**
* Exception used to abort startup due to validation failures without producing a noisy stack trace.
*/
public class StartupAbortException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;

public StartupAbortException(String message) {
super(message);
}

@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
Loading
Loading