Specifically, I want Tomcat to abort start-up when my web app can't initialize (due to a configuration error).
I'm tried to use the Tomcat shutdown port. That's not ready when my web app is starting up.
-Dorg.apache.catalina.startup.EXIT_ON_INIT_FAILURE=true, doesn't work. Tomcat starts up just fine.
Throw an uncaught Exception. I've tried throwing an exception. Tomcat still initializes correctly but my web app is in a non-functional state.
Use the Tomcat JMX API. None of the shutdown methods will cause Tomcat to shut down.
So far, the only other option I've got is System.exit(-1). That does work, but I'm concerned that doing that to Tomcat will cause problems.
Tomcat is designed to be resilient against badly written applications that don't start up.
org.apache.catalina.startup.EXIT_ON_INIT_FAILURE is for stopping Tomcat if one of Tomcat's components, such as one of the connectors, doesn't start (e.g. because the port is in use).
System.exit() is ugly but it will work and it is safe as far as Tomcat is concerned (as for that matter is kill -9). It is applications that may not like this rather brutal approach to shutdown.
There is a Tomcat specific way to do this more cleanly. Add a custom LifecycleListener to the Server component and have it respond to AFTER_START_EVENT. Have the listener navigate the Container hierarchy from the Server to the Contexts [Server->Service(s)->Engine-Host(s)->Context(s)] and check that each context is in the state STARTED. If not, call stop() and then destroy() on the Server.
Here's an implementation of the listener which aborts the Tomcat startup, as mentioned by Mark Thomas. StrictStateCheckListener is an after-start server listener. It checks the state of all Container, including engine, host, and context. The server start-up will be aborted if there's any component failed to start (state different from STARTED).
package com.example.lifecycle;
import java.util.logging.*;
import org.apache.catalina.*;
public class StrictStateCheckListener implements LifecycleListener {
private static final Logger LOGGER = Logger.getLogger(StrictStateCheckListener.class.getName());
private boolean hasFailures;
private String containerState;
#Override
public void lifecycleEvent(LifecycleEvent event) {
String type = event.getType();
Lifecycle lifecycle = event.getLifecycle();
if (lifecycle instanceof Server && type.equals(Lifecycle.AFTER_START_EVENT)) {
Server server = (Server) lifecycle;
hasFailures = false;
// Check status of each container
StringBuilder sb = new StringBuilder("Status:").append(System.lineSeparator());
for (Service service : server.findServices()) {
checkState(service.getContainer(), sb, "");
}
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.info(sb.toString());
}
if (hasFailures) {
/*
* The server will not be stopped by this listener. Indeed,
* an exception is raised to trigger shutdown hook. See:
* `org.apache.catalina.startup.Catalina`.
*/
throw new IllegalStateException(containerState);
}
}
}
private void checkState(Container container, StringBuilder report, String indent) {
if (!hasFailures && container.getState() != LifecycleState.STARTED) {
hasFailures = true;
containerState = stateOf(container);
}
report.append(indent).append(stateOf(container));
for (Container child : container.findChildren()) {
checkState(child, report, indent + " ");
}
}
private String stateOf(Container container) {
String className = container.getClass().getSimpleName();
String stateName = container.getStateName();
return String.format("%s[%s]: %s%n", className, container.getName(), stateName);
}
}
Register this listener to your server component via server.xml:
<Server>
...
<Listener className="com.example.lifecycle.StrictStateCheckListener" />
...
</Server>
Your point No 1
Do you mean when uninitialized state exception is thrown from application you are calling tomcat shutdown.bat file.Have you tried to call shutdown.bat file from your program.
I know this question is a bit old, but I'm going a bit further here thinking out of the box to solution you are seeking. It looks like your requirement is to stop tomcat when your webapp fails to initialize. This is a bit different than "abort Tomcat start-up". Tomcat itself is already started and will start to deploy your webapp right after it started up. Based on your requirement, I assume that you don't have any other webapp. If that's the case, why don't you just use Spring Boot? Spring boot provides a way to deploy self contained web application. In this case, if initialization fails, application will not start.
Related
I have built a web application that uses
SpringBoot v1.3.6.RELEASE
Tomcat 8.0.36
Java 1.8u101
on CentOS 7.2
The web application is also a SOAP client that calls out to another web application.(JAX-WS RI 2.2.9) If the applications remains idle for 15 seconds the first webservice call stalls for nearly 2 seconds. It appears that the stall happens in o.a.c.loader.WebappClassLoaderBase.
After idle 15 seconds
16:02:36.165 : Delegating to parent classloader org.springframework.boot.loader.LaunchedURLClassLoader#45283ce2
16:02:36.170 : Searching local repositories
16:02:36.170 : findResource(META-INF/services/javax.xml.soap.MetaFactory)
16:02:38.533 : --> Resource not found, returning null
16:02:38.533 : --> Resource not found, returning null
Next request no idle time
16:07:09.981 : Delegating to parent classloader org.springframework.boot.loader.LaunchedURLClassLoader#45283ce2
16:07:09.984 : Searching local repositories
16:07:09.985 : findResource(META-INF/services/javax.xml.soap.MetaFactory)
16:07:09.986 : --> Resource not found, returning null
16:07:09.986 : --> Resource not found, returning null
16:07:09.988 : findResources(META-INF/services
All above messages produced by o.a.c.loader.WebappClassLoaderBase and they are apparently being caused by ClientSOAPHandlerTube.processRequest which is from JAX-WS RI.
You'll notice the first call takes over 2 seconds but subsequent calls take only milliseconds.
I'm wondering if anyone has experienced this behavior?
Possible solutions:
Is it possible to change out the classloader used by tomcat in springboot to use ParallelWebappClassLoader
Or maybe this is a product of the reloadable flag on the classloader but I don't see how to change that flag in springboot.
When run using Jetty as the container this does not occur.
Final Solution: (thanks to Gergely Bacso)
#Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
}
}
private void customizeTomcat(TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory) {
tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {
#Override
public void customize(Context cntxt) {
cntxt.setReloadable(false);
}
});
}
};
}
Actually your findings are quite good and you have 90% answered your question already. These two facts:
"it appears that the stall happens in o.a.c.loader.WebappClassLoaderBase"
"when run using Jetty as the container this does not occur."
show that it is going be a Tomcat-related problem because:
o.a.c. stands for org.apache.catalina
Your code works well on another container. (Jetty)
You also observed, that the issue is happening after 15 seconds of idle time. This perfectly corresponds to Tomcat's default checkInterval setting, which is:
The number of seconds between checks for modified classes and
resources, if reloadable has been set to true. The default is 15
seconds.
So in short: currently your reloadable flag is ON, and Tomcat tries to reload your classes which is handy during development, but unacceptable in any other case. The way to switch it off is not via Spring-boot though.
SOLUTION:
You need to locate your context.xml / server.xml where you will find your Context defined like this:
<Context ... reloadable="true">
Remove the reloadable flag, and you have solved the problem. The file itself can be either in $CATALINA_BASE/conf of $CATALINE_HOME/conf, but in reality these locations can be a bit tricky to find if you are using some IDE to manage Tomcat for you.
In case of embedded Tomcat with Spring-boot:
The class you can use to manipulate Tomcat settings is: EmbeddedServletContainerCustomizer.
Through this you can add a TomcatContextCustomizer (addContextCustomizers) so that you can call setReloadable on the context itself.
I do not see any reason for Spring-boot needing this flag on true.
A similar question has been answered...
Here: Listener for server starup and all spring bean loaded completely
Here: How to add a hook to the application context initialization event?
and several other places.
In my primary application, when the Root Spring context initializes it will trigger three child contexts to initialize. When listening for an event to fire (based on the ContextRefreshedEvent), this results in four total events. This does not give the consumer of these events accurate information regarding the overall state of the application's Spring context.
Unfortunately I don't have the ability to change the primary application. The code that I'm wrestling with now is packaged as a jar and loaded into the primary application using a plugin architecture. I am able to hook into the application's Spring context without issue, and I can successfully receive ContextRefreshEvent triggers.
What I'm looking for is a way to understand if all Spring application contexts in the primary application have completed. One thing I tried was to manually keep track of the starting and completing of the application context (using a map) so that I could tell when all known application contexts were finished initializing. This didn't work for me, as I found that the ContextStartedEvent didn't trigger during Spring's refresh operation.
Additional things that could work for me would be knowing how many application contexts exist or how many beans there are that need to be loaded. That way I could keep track of them as they finished and ultimately know that all application contexts are complete.
Any ideas would be appreciated.
EDIT
In an attempt to ask as pointed a question as possible, I made this question too prescriptive in terms of the implementation. Here is the actual problem that I was trying to solve (in the form of a question):
How might a plugin, packaged as a jar inside of a webapp, tell when the context of the webapp has been deployed successfully in Tomcat?
Through testing, I thought it was fairly safe to assert that the initialization of the Spring context was synonymous with the deploy phase of the application through Tomcat. This may be false, but it lined up well enough.
Broadening the scope of the fix from exclusively using Spring, I was able to come up with a solution to the problem. I will post that as a separate answer.
As of Spring 2.5.X < 4.X
One thing you could do is listen for ContextStartedEvent-s, add them to a map and then once ContextRefreshedEvent-s are triggered, wait while all contexts are refreshed.
This still might be not safe if the context is refreshed multiple times though...but at least a place to start.
As of Spring 4+
Reading ContextRefreshedEvent javadocs:
Event raised when an {#code ApplicationContext} gets initialized or refreshed.
What this means is that you can have your own logic to track whether that context was initialized or refreshed.
#EventListener( { ContextRefreshedEvent.class } )
void contextRefreshedEvent( ContextRefreshedEvent e ) {
ApplicationContext context = ( ApplicationContext ) e.getSource();
System.out.println( "a context refreshed event happened for context: " + context.getDisplayName() );
}
Meaning - the first time you'll get an event with that context - you will know that it was initialized. The following times - you will know that it was refreshed.
Being brutally simple ( no optimizations ) you can do the following:
private final Map<String, String> contextStatuses = new HashMap<>();
#EventListener( { ContextRefreshedEvent.class } )
public void contextRefreshedEvent( ContextRefreshedEvent e ) {
ApplicationContext context = ( ApplicationContext ) e.getSource();
if ( !contextStatuses.containsKey( context.getDisplayName() ) ) { // initialized
contextStatuses.put( context.getDisplayName(), "initialized" );
} else { // refreshed
contextStatuses.put( context.getDisplayName(), "refreshed" );
}
checkAllContextsRefreshed();
}
private void checkAllContextsRefreshed() {
for ( String status : contextStatuses.values() ) {
if ( !"refreshed".equals( status ) ) {
return;
}
}
doWhenAllContextsRefreshed();
}
Per the edit on my question, here is the solution that I went with:
In the plugin that is packaged inside the primary application, I added a check using JMX against the stateName attribute of the WebModule type under Catalina. This isn't the exact code I used, but for simplicity the logic looks like this:
ObjectName name = new ObjectName("Catalina:j2eeType=WebModule,name=//localhost/" + contextPath + ",J2EEApplication=none,J2EEServer=none");
MBeanServer mx = ManagementFactory.getPlatformMBeanServer();
String state = String.valueOf(mx.getAttribute(name, "stateName"));
if (state.equals("STARTED")) {
LOGGER.warn("SUCCESS!");
}
As I mentioned in the edit, equating Tomcat deploy to Spring context initialization was close, but ultimately it wasn't a fair comparison. Using this JMX endpoint gives me a very accurate representation of the state of the Tomcat deploy.
I'm currently searching for a way to stop a deployment on wildfly programmatically.
Background:
The application does some health checks in its startup phase using an MBean.
If the app determines that the environment is not valid because some resources are missing, it needs to stop its own deployment.
The way it was:
The application was formerly running on JBoss 4 and simply stopped the whole app server calling the shutdown command using JMX.
In case this command failed, it simply terminated the whole JVM using System.exit(1).
Current problems:
Calling shutdown() via JMX does not work on wildfly since the whole server hangs when trying to stop it from within a deployed application.
System.exit() will also not work since wildly must be catching the command in any way.
So does anyone know how to stop the server from within the deployment or stop the deployment process or undeploy the app?
Thanks a lot!
I assume the core question is stopping the deployment process if some health checks fail. Throwing a run-time exception during app startup is enough to do the job.
#Startup
#Singleton
public class StartupBean {
#PostConstruct
public void start() {
//your checks
boolean check = doHealthCheck();
if(!check){
throw new RuntimeException("Your error message");
}
}
}
or
#Startup
#Singleton
public class StartupBean {
#PostConstruct
public void start() {
//your checks
boolean check = doHealthCheck();
if(!check){
throw new Error("Your error message");
}
}
}
I suggest you to try WildFly CLI:
Running the CLI
or use Marker Files.
But in any case, I'm not sure how the server will behave. For example what will happen when You add file myWarName.dodeploy when there is myWarName.isdeploying. So let us know when You will earn some experience in this topic (it is quite interesting).
Ok, I did not yet manage to undeploy the app but I've been able to shutdown the server in case of an error. This is not perfect but matches the behavior of the app on the older version of JBoss, so I think it's not too bad at all.
I'm now calling the CLI interface like so
try {
String jbossBinDir = System.getProperty("jboss.server.base.dir").replace("standalone", "bin");
Runtime.getRuntime().exec("sh " + jbossBinDir + "/jboss-cli.sh -c command=:shutdown");
} catch(IOException e) {
...
}
This works reliable for us.
In my comment above I stated that the execution returns with an error code but this was probably the case because I must have had a typo in the command call.
We're using a CDI Extension to abort the deployment if our DB schema doesn't match the application's expectation:
class MyValidatingExtension implements javax.enterprise.inject.spi.Extension {
void deploymentValidationFinished(#Observes AfterDeploymentValidation afterDeploymentValidation) {
if (!stateExpected) {
afterDeploymentValidation.addDeploymentProblem(new IDontLikeThisException());
}
}
}
The deployment of the WAR will fail with the stacktrace of the exception listed as DeploymentProblem, leaving your WAR in an undeployed state. This solution is independent of your container implementation, it uses a CDI standard mechanism only. Note that this will not stop/shutdown the server!
I have a java web application, that has a very strict security requirement. on Start-up it tries to configure the web container to force it use SSL session id to establish the http session id. If it fails in this configuration I want the app to stop and not process an request. I can use System.exit() as I am doing in the sample below, but I am wondering if there is a nice way to do this without killing the JVM. Also a security manager can get in the way of System.exit()
I am using tomcat 7.
public class SessionTrackingModeListener implements ServletContextListener
{
#Override
public void contextInitialized(ServletContextEvent event)
{
try
{
ServletContext context = event.getServletContext();
EnumSet<SessionTrackingMode> modes = EnumSet.of(SessionTrackingMode.SSL);
context.setSessionTrackingModes(modes);
System.out.println("SSL based Session tracking enabled");
} catch (Exception e)
{
System.err.println("Unable to setup SSL based session tracking");
e.printStackTrace();
System.exit(1);
// Question How do I get the container to not start the app without using exit?
}
}
#Override
public void contextDestroyed(ServletContextEvent event)
{
}
}
First of all I think it is not a good idea to use
System.exit(1)
in a servlet environment, since some web containers may use a security manager to prevent your webapp to kill the entire server.
The servlet spec is not strict about what happens when you throw a runtime exception in the contextInitialized function. In my experience the servlet containers abort the startup of the webapp, but again it may depend on your container so you should test it.
If you are using plain old servlets it maybe a good choice to create a ServletFilter which checks whether the security constraints are ok or redirect the request to an error page.
Would you please explain more about "want the app to stop and not process an request".You mean yours web application OR web container.Do you want to show some human readable message on web application when there is some problem in container to user ?.As when there is exception the tomcat does not get started start.You have to start it again .
I'm using embedded Jetty to launch a standard Java webapp. My launcher is something like this:
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.component.LifeCycle.Listener;
import org.eclipse.jetty.webapp.WebAppContext;
...
Listener listener = ...;
Server server = new Server(8080);
WebAppContext webapp = new WebAppContext();
...
webapp.addLifeCycleListener(listener);
server.setHandler(webapp);
server.start();
...
This all works fine in that I can start my app and browse to it and everything appears to be working.
But now I'm trying to add error reporting to my launcher. I've temporarily set my webapp to throw an exception in the contextInitialized() method of a ServletContextListener. The exception is thrown, and I get a log message of
ERROR org.eclipse.jetty.util.log Failed startup of context WebAppContext#...
but my LifeCycleListener does not receieve any failure event. In fact, it receives a started event, and the WebAddContext passed to the listener returns false for LifeCycle#isFailed() and true for LifeCycle#isRunning().
Browsing to my webapp results in 503 Service Unavailable errors.
This happens in Jetty versions 7.0.1.v20091125 and 7.2.1.v20101111. See Jetty 7 api docs.
As per my comments to Heri's answer, WebAppContext swallows exceptions. They would otherwise be caught by AbstractLifeCycle and failure events sent out. This gets me most of the way there:
public class ThrowyWebAppContext extends WebAppContext {
#Override
protected void doStart() throws Exception {
super.doStart();
if (getUnavailableException() != null) {
throw (Exception) getUnavailableException();
}
}
}
If I remember correctly failed life cycles of context elements are not propagated to the life cycle of the context itself (like failed contexts are not propagated to the life cycle of the server itself). Check the life cycle of the SecurityHandler, ServletHandler and SessionHandler of the context, too.