csutherl commented on code in PR #928: URL: https://github.com/apache/tomcat/pull/928#discussion_r2643761164
########## java/org/apache/catalina/startup/validator/StartupValidationListener.java: ########## @@ -0,0 +1,158 @@ +/* + * 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.validator; + +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.Server; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.res.StringManager; + +/** + * A lifecycle listener that runs configuration validators during server startup + * as a pre-flight check. This allows catching configuration errors and aborting + * startup for issues that may not normally stop the server. + * + * <p>The listener runs at the BEFORE_INIT_EVENT, which occurs after the server + * configuration has been parsed but before the server attempts to bind to any ports. + * This ensures that port availability checks and other validations can run without + * interference from the server itself. + * + * <p>Configuration options (set as listener attributes): + * <ul> + * <li><b>abortOnError</b> - If true, abort startup if validation errors are found. + * Default: false</li> + * <li><b>logWarnings</b> - If true, log warnings to the console/logs. + * Default: true</li> + * <li><b>logInfo</b> - If true, log informational messages. + * Default: false</li> + * </ul> + * + * <p>Example listener configuration with options: + * <pre> + * <Listener className="org.apache.catalina.startup.validator.StartupValidationListener" + * abortOnError="true" + * logWarnings="true" + * logInfo="false" /> + * </pre> + */ +public class StartupValidationListener implements LifecycleListener { + + private static final Log log = LogFactory.getLog(StartupValidationListener.class); + private static final StringManager sm = StringManager.getManager(StartupValidationListener.class); + + private boolean abortOnError = false; + private boolean logWarnings = true; + private boolean logInfo = false; + + /** + * Sets whether to abort startup if validation errors are found. + * + * @param abortOnError true to abort on errors + */ + public void setAbortOnError(boolean abortOnError) { + this.abortOnError = abortOnError; + } + + /** + * Sets whether to log warning messages. + * + * @param logWarnings true to log warnings + */ + public void setLogWarnings(boolean logWarnings) { + this.logWarnings = logWarnings; + } + + /** + * Sets whether to log informational messages. + * + * @param logInfo true to log info messages + */ + public void setLogInfo(boolean logInfo) { + this.logInfo = logInfo; + } + + @Override + public void lifecycleEvent(LifecycleEvent event) { + // Run before init() to check ports before they're bound, + // return for any other events. + if (!Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) { + return; + } + + if (!(event.getLifecycle() instanceof Server)) { + log.warn(sm.getString("startupValidationListener.notServer")); + return; + } + + Server server = (Server) event.getLifecycle(); + + if (log.isInfoEnabled()) { + log.info(sm.getString("startupValidationListener.starting")); + } + + // Init registry and validate server config + ValidatorRegistry registry = new ValidatorRegistry(); + ValidationResult result = registry.validate(server); + + // Log findings + logFindings(result); + + // Should we abort now? + if (abortOnError && result.getErrorCount() > 0) { + String message = sm.getString("startupValidationListener.abortingOnErrors", + String.valueOf(result.getErrorCount())); + throw new IllegalArgumentException(message); Review Comment: Yeah, that could work I think, but it may still be a bit ugly, no? Ideally I'd completely suppress the exception and just expect that the user take the SEVERE log line saying that it's aborting startup and that be enough while also not logging the stack for the Connector failure to bind to the port. Current output: ``` 23-Dec-2025 11:36:40.378 SEVERE [main] org.apache.catalina.startup.validator.StartupValidationListener.logFindings Port 8080 (HTTP/1.1, Catalina): Port 8080 is already in use 23-Dec-2025 11:36:40.379 SEVERE [main] org.apache.catalina.startup.Catalina.loadInternal Error initializing Catalina org.apache.catalina.LifecycleException: Failed to initialize component [StandardServer[8005]] at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:405) at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:125) at org.apache.catalina.startup.Catalina.loadInternal(Catalina.java:728) .... Caused by: java.lang.IllegalArgumentException: Aborting startup due to 1 configuration errors at org.apache.catalina.startup.validator.StartupValidationListener.lifecycleEvent(StartupValidationListener.java:121) .... 23-Dec-2025 11:36:40.379 INFO [main] org.apache.catalina.startup.Catalina.loadInternal Server initialization in [98] milliseconds 23-Dec-2025 11:36:40.511 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina] 23-Dec-2025 11:36:40.511 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet engine: [Apache Tomcat/12.0.0-M1-dev] .... 23-Dec-2025 11:36:40.763 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"] 23-Dec-2025 11:36:40.766 SEVERE [main] org.apache.catalina.util.LifecycleBase.handleSubClassException Failed to initialize component [Connector["http-nio-8080"]] org.apache.catalina.LifecycleException: Protocol handler initialization failed .... Caused by: java.net.BindException: Address already in use .... 23-Dec-2025 11:36:40.766 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"] 23-Dec-2025 11:36:40.766 SEVERE [main] org.apache.catalina.util.LifecycleBase.handleSubClassException Failed to start component [Connector["http-nio-8080"]] org.apache.catalina.LifecycleException: Protocol handler start failed .... Caused by: java.net.BindException: Address already in use .... 23-Dec-2025 11:36:40.767 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [386] milliseconds 23-Dec-2025 11:36:40.768 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"] ``` Note that all of the component startup startup message appear as if there was no intentional abort happening :( We go straight from what looks like a successful Server startup to it being destroyed in the last two lines. Better(?), cleaner output: ``` 23-Dec-2025 11:36:40.378 SEVERE [main] org.apache.catalina.startup.validator.StartupValidationListener.logFindings Port 8080 (HTTP/1.1, Catalina): Port 8080 is already in use 23-Dec-2025 11:36:40.379 SEVERE [main] org.apache.catalina.startup.Catalina.loadInternal Error initializing Catalina <Messages indicating things are destroyed> ``` I don't think the current architecture really allows for what I want though. A `System.exit()` would do the trick :P -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
