Reputation: 8777
I have a ServletContextListener which performs some database management functions when my Java EE application starts. This runs in my application before JPA and other pieces of the application are started/loaded. If the database maintenance fails I am logging the errors. If the database maintenance fails the application will not function properly and I would like to halt the application.
How can I gracefully and correctly stop the application from ServletContextListener.contextInitialized?
Solution given by Viven below is close but not quite. When I throw a RuntimeException Glassfish is left in an inconsistent state where its admin console is not accessible but some process is still running and keeping port 3700 (IIOP?) open which then prevents a restart.
Upvotes: 10
Views: 9330
Reputation: 583
Here is how to gracefully stop the Web application on Tomcat 11* without stopping Tomcat itself:
package com.example;
import static java.util.logging.Level.SEVERE;
import static javax.xml.xpath.XPathConstants.STRING;
import java.io.File;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.sql.Connection;
import java.util.Hashtable;
import java.util.logging.Logger;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
public class ExampleServletContextListener implements ServletContextListener {
/** Standard logger */
static final Logger LOGGER = Logger.getLogger(ExampleServletContextListener.class.getName());
/**
* Data Source defined in <code>META-INF/context.xml</code>, e.g.
* <code>
* <Resource name="jdbc/example_data_source" auth="Container"
* type="javax.sql.DataSource" maxTotal="100" maxIdle="30"
* maxWaitMillis="10000" username="sa" password="password"
* driverClassName="org.h2.Driver"
* url="jdbc:h2:mem:example;DB_CLOSE_DELAY=-1" />
* </code>
*/
static final String DATA_SOURCE = "java:/comp/env/jdbc/example_data_source";
/**
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(final ServletContextEvent event) {
try {
final InitialContext initialContext = new InitialContext();
final DataSource dataSource = (DataSource) initialContext.lookup(DATA_SOURCE);
try(final Connection connection = dataSource.getConnection()) {
// Test the connection
}
} catch (final Exception e) {
LOGGER.log(SEVERE, e.getMessage(), e);
gracefulStop(event.getServletContext());
}
}
/**
* Gracefully stop the Web Application
* @param servletContext the Application context facade
*/
static void gracefulStop(final ServletContext servletContext) {
try(final InputStream is = servletContext.getResourceAsStream("/WEB-INF/web.xml")) {
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
final XPath xpath = XPathFactory.newInstance().newXPath();
final Document webXmlDocument = documentBuilder.parse(is);
final String contextName = (String) xpath.evaluate("/web-app/@id", webXmlDocument, STRING);
final String catalinaHome = System.getProperty("catalina.home", System.getenv("CATALINA_HOME"));
assert catalinaHome != null;
final File serverXmlFile = new File(new File(new File(catalinaHome), "conf"), "server.xml");
final Document serverXmlDocument = documentBuilder.parse(serverXmlFile);
final String serviceName = (String) xpath.evaluate("/Server/Service/@name", serverXmlDocument, STRING); // Default is "Catalina"
final String hostName = (String) xpath.evaluate("/Server/Service[@name='"+serviceName+"']/Engine/Host/@name", serverXmlDocument, STRING); // Default is "localhost"
final Hashtable<String, String> table = new Hashtable<>();
table.put("j2eeType", "WebModule");
table.put("name", "//" + hostName + "/" + contextName);
table.put("J2EEApplication", "none");
table.put("J2EEServer", "none");
final MBeanServerConnection mbeanServerConnection = ManagementFactory.getPlatformMBeanServer();
final ObjectName webappObjectName = ObjectName.getInstance(serviceName, table);
mbeanServerConnection.invoke(webappObjectName, "stop", null, null);
} catch (final Exception e) {
LOGGER.log(SEVERE, e.getMessage(), e);
}
}
}
*) Using Tomcat 11 because it uses the new jakarta.servlet
package naming. If you wish to use a former version of Tomcat, change it to javax.servlet
.
Footnote: this answer is based on: You can shutdown a single application in Tomcat, using MBeans/JMX
Upvotes: 1
Reputation: 20895
If your ServletContextListener
throws an exception, the webapp won't load correctly and the application server may block all subsequent request (and respond with a 500 error).
It isn't exactly preventing the application to start, nor stopping the application, but it prevents any further usage of the app and could be useful in your case.
After proper verification in the spec, this behaviour isn't mandatory in the specification. The server may (not must) return 500 errors. This solution has to be used carefully, therefore.
See this Answer for a quote from the Servlet spec.
Upvotes: 5
Reputation: 5694
In your listener, catch any exceptions and use servlet context attributes to store flags or other useful information about the error. You should probably also log something to indicate that the app is non-functional.
At this point, your options may be dictated by the architecture of your app. If all requests are handled by a single controller/dispatcher servlet, it might make sense to have its init
method check the context attributes and throw an UnavailableException
. Just be aware that the exception only applies to the specific servlet throwing it. This makes the approach less manageable if your app contains many servlets or allows direct access to other resources.
Another option would be to create a filter that intercepts every request, checks the context attributes and then throws an exception. Other variations are certainly possible.
Upvotes: 5