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