001package io.ebean.event;
002
003import io.ebean.Database;
004import io.ebean.service.SpiContainer;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008import java.sql.Driver;
009import java.sql.DriverManager;
010import java.sql.SQLException;
011import java.util.ArrayList;
012import java.util.Enumeration;
013import java.util.List;
014import java.util.concurrent.locks.ReentrantLock;
015
016/**
017 * Manages the shutdown of the JVM Runtime.
018 * <p>
019 * Makes sure all the resources are shutdown properly and in order.
020 * </p>
021 */
022public final class ShutdownManager {
023
024  private static final Logger logger = LoggerFactory.getLogger(ShutdownManager.class);
025
026  private static final ReentrantLock lock = new ReentrantLock();
027
028  private static final List<Database> databases = new ArrayList<>();
029
030  private static final ShutdownHook shutdownHook = new ShutdownHook();
031
032  private static boolean stopping;
033
034  private static SpiContainer container;
035
036  static {
037    // Register the Shutdown hook
038    registerShutdownHook();
039  }
040
041  /**
042   * Disallow construction.
043   */
044  private ShutdownManager() {
045  }
046
047  public static void registerContainer(SpiContainer ebeanContainer) {
048    container = ebeanContainer;
049  }
050
051  /**
052   * Make sure the ShutdownManager is activated.
053   */
054  public static void touch() {
055    // Do nothing
056  }
057
058  /**
059   * Return true if the system is in the process of stopping.
060   */
061  public static boolean isStopping() {
062    lock.lock();
063    try {
064      return stopping;
065    } finally {
066      lock.unlock();
067    }
068  }
069
070  /**
071   * Deregister the Shutdown hook.
072   * <p>
073   * In calling this method it is expected that application code will invoke
074   * the shutdown() method.
075   * </p>
076   * <p>
077   * For running in a Servlet Container a redeploy will cause a shutdown, and
078   * for that case we need to make sure the shutdown hook is deregistered.
079   * </p>
080   */
081  public static void deregisterShutdownHook() {
082    lock.lock();
083    try {
084      Runtime.getRuntime().removeShutdownHook(shutdownHook);
085    } catch (IllegalStateException ex) {
086      if (!ex.getMessage().equals("Shutdown in progress")) {
087        throw ex;
088      }
089    } finally {
090      lock.unlock();
091    }
092  }
093
094  /**
095   * Register the shutdown hook with the Runtime.
096   */
097  protected static void registerShutdownHook() {
098    lock.lock();
099    try {
100      String value = System.getProperty("ebean.registerShutdownHook");
101      if (value == null || !value.trim().equalsIgnoreCase("false")) {
102        Runtime.getRuntime().addShutdownHook(shutdownHook);
103      }
104    } catch (IllegalStateException ex) {
105      if (!ex.getMessage().equals("Shutdown in progress")) {
106        throw ex;
107      }
108    } finally {
109      lock.unlock();
110    }
111  }
112
113  /**
114   * Shutdown gracefully cleaning up any resources as required.
115   * <p>
116   * This is typically invoked via JVM shutdown hook.
117   * </p>
118   */
119  public static void shutdown() {
120    lock.lock();
121    try {
122      if (stopping) {
123        // Already run shutdown...
124        return;
125      }
126
127      if (logger.isDebugEnabled()) {
128        logger.debug("Shutting down");
129      }
130
131      stopping = true;
132
133      deregisterShutdownHook();
134
135      String shutdownRunner = System.getProperty("ebean.shutdown.runnable");
136      if (shutdownRunner != null) {
137        try {
138          // A custom runnable executed at the start of shutdown
139          Runnable r = (Runnable) ClassUtil.newInstance(shutdownRunner);
140          r.run();
141        } catch (Exception e) {
142          logger.error("Error running custom shutdown runnable", e);
143        }
144      }
145
146      if (container != null) {
147        // shutdown cluster networking if active
148        container.shutdown();
149      }
150
151      // shutdown any registered servers that have not
152      // already been shutdown manually
153      for (Database server : databases) {
154        try {
155          server.shutdown();
156        } catch (Exception ex) {
157          logger.error("Error executing shutdown runnable", ex);
158          ex.printStackTrace();
159        }
160      }
161
162      if ("true".equalsIgnoreCase(System.getProperty("ebean.datasource.deregisterAllDrivers", "false"))) {
163        deregisterAllJdbcDrivers();
164      }
165    } finally {
166      lock.unlock();
167    }
168  }
169
170  private static void deregisterAllJdbcDrivers() {
171    // This manually deregisters all JDBC drivers
172    Enumeration<Driver> drivers = DriverManager.getDrivers();
173    while (drivers.hasMoreElements()) {
174      Driver driver = drivers.nextElement();
175      try {
176        logger.info("Deregistering jdbc driver: " + driver);
177        DriverManager.deregisterDriver(driver);
178      } catch (SQLException e) {
179        logger.error("Error deregistering driver " + driver, e);
180      }
181    }
182  }
183
184  /**
185   * Register an ebeanServer to be shutdown when the JVM is shutdown.
186   */
187  public static void registerDatabase(Database server) {
188    lock.lock();
189    try {
190      databases.add(server);
191    } finally {
192      lock.unlock();
193    }
194  }
195
196  /**
197   * Deregister an ebeanServer.
198   * <p>
199   * This is done when the ebeanServer is shutdown manually.
200   * </p>
201   */
202  public static void unregisterDatabase(Database server) {
203    lock.lock();
204    try {
205      databases.remove(server);
206    } finally {
207      lock.unlock();
208    }
209  }
210
211  private static class ShutdownHook extends Thread {
212    @Override
213    public void run() {
214      ShutdownManager.shutdown();
215    }
216  }
217}