Use filter to keep a list of all critical queries.
When a “prepare shutdown” request is received, the filter should begin to reject some requests.
Write a servlet that indicates how many critical jobs remain in the queue.
In the shutdown tool, send the message "make shutdown". Polling a servlet for the number of critical jobs. When it reaches 0, send the actual shutdown command.
To make this happen, create a service at the business level that organizes it. Note that everything must happen before calling contextDestroyed() ! Your closure of a special application does not fit into J2EE's view of the world, so you need to manage it yourself.
The service should be able to tell stakeholders about the completion of work, the number of critical tasks, etc. Servlets and filters can then use this service to refuse requests or to indicate how many jobs are left.
When all tasks are completed, reject all requests except access to the shutdown info servlet, which should then indicate that the application is now ready for death.
Write a tool that gives administrators a good user interface to initiate a shutdown of your application.
[EDIT] You may be tempted to prevent the OS from quitting the application. Do not do this.
What you need to do is write a special tool to close the application using the two-phase process described above. This should be a standard shutdown method.
Yes, administrators will complain about it. On Unix, you can hide this tool by placing it in an init script, so no one will notice. Windows may have a similar solution.
Killing a server should always be possible to stop it in the case of (un) expected circumstances, such as: errors in your code being turned off, emergency shutdown during a power failure, errors in the application code, or when Murphy occurs.