/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.core; import java.io.ByteArrayInputStream; import java.io.File; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import javax.annotation.Resource; import javax.ejb.EJB; import javax.ejb.Singleton; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.cloud.Server; import org.rhq.core.domain.cloud.Server.OperationMode; import org.rhq.core.util.file.FileUtil; import org.rhq.core.util.jdbc.JDBCUtil; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.cloud.TopologyManagerLocal; import org.rhq.enterprise.server.cloud.instance.ServerManagerLocal; import org.rhq.enterprise.server.scheduler.SchedulerLocal; import org.rhq.enterprise.server.util.LookupUtil; /** * <p>This listens for the RHQ server's shutdown notification and when it hears it, will start shutting down RHQ * components that need to clean up.</p> * * <p>This session bean will depend on every over session bean in the deployment. Its class name is processed by the * org.rhq.enterprise.startup.deployment.RhqShutdownDependenciesProcessor which is installed by the RHQ Startup * Subsystem. This is completely AS7 specific and is a work around AS7 shutting down session beans in parallel.</p> * * <p><strong>DO NOT CHANGE THIS CLASS FULLY QUALIFIED NAME</strong> org.rhq.enterprise.server.core.ShutdownListener * or make the change in the DUP accordingly.</p> * * @author John Mazzitelli * @author Jay Shaughnessy * @author Joseph Marques */ @Singleton @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class ShutdownListener { private final Log log = LogFactory.getLog(ShutdownListener.class); private final String RHQ_SHUTDOWN_TIME_LOG_FILE = "rhq-shutdown-time.dat"; private final String RHQ_DB_TYPE_MAPPING_PROPERTY = "rhq.server.database.type-mapping"; @EJB private SchedulerLocal schedulerBean; @EJB private ServerManagerLocal serverManager; @EJB private TopologyManagerLocal topologyManager; @Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME) private DataSource dataSource; @Resource(name = "NoTx_RHQ_DS", mappedName = RHQConstants.NO_TX_DATASOURCE_JNDI_NAME) @SuppressWarnings("unused") // Prevent the server from closing the NoTx datasource before RHQ EAR // NoTxDS is used by the Quartz scheduler private DataSource noTxDataSource; private File shutdownTimeLogFile; /** * This is called when the shutdown notification is received from the JBoss server. This gives a chance for us to * cleanly shutdown our application in an orderly fashion. */ public void handleNotification() { log.info("Shutdown listener has been told we are shutting down - starting to clean up now..."); logShutdownTime(); stopScheduler(); updateServerOperationMode(); stopEmbeddedDatabase(); log.info("Shutdown listener completed its shutdown tasks. It is safe to shutdown now."); } /** * This is the file that the {@link ShutdownListener} will write to when it logs * the last time the server was shutdown. * * @return shutdown time log file location * @throws Exception */ public File getShutdownTimeLogFile() throws Exception { if (shutdownTimeLogFile == null) { try { CoreServerMBean coreServer = LookupUtil.getCoreServer(); File dataDir = coreServer.getJBossServerDataDir(); File timeFile = new File(dataDir, RHQ_SHUTDOWN_TIME_LOG_FILE); if (!timeFile.exists()) { ByteArrayInputStream data = new ByteArrayInputStream("0".getBytes()); FileUtil.writeFile(data, timeFile); } shutdownTimeLogFile = timeFile; } catch (Exception e) { // only show ugly stack traces if the user runs the server in debug mode if (log.isDebugEnabled()) { log.warn("Failed to get shutdown time log file", e); } else { log.warn("Failed to get shutdown time log file: " + e.getMessage()); } throw e; } } return shutdownTimeLogFile; } /** * Stores the current epoch millis time in a file in the data directory. This is used by the * StartupBean so it knows how long to wait (if at all) before completing startup (this is to * give enough time to allow agents to realize the server has been down). */ private void logShutdownTime() { try { File shutdownTimeLogFile = getShutdownTimeLogFile(); ByteArrayInputStream input = new ByteArrayInputStream(String.valueOf(System.currentTimeMillis()).getBytes()); FileUtil.writeFile(input, shutdownTimeLogFile); } catch (Throwable t) { // only show ugly stack traces if the user runs the server in debug mode if (log.isDebugEnabled()) { log.warn("Failed to store shutdown time", t); } else { log.warn("Failed to store shutdown time: " + t.getMessage()); } } } /** * This will shutdown the scheduler. */ private void stopScheduler() { try { if (schedulerBean.isStarted()) { log.info("Shutting down the scheduler gracefully - currently running jobs will be allowed to finish..."); schedulerBean.shutdown(true); log.info("The scheduler has been shutdown and all jobs are done."); } else log.info("No need for shutting down the scheduler, because it is not running."); } catch (Throwable t) { // only show ugly stack traces if the user runs the server in debug mode if (log.isDebugEnabled()) { log.warn("Failed to shutdown the scheduler", t); } else { log.warn("Failed to shutdown the scheduler: " + t.getMessage()); } } } private void updateServerOperationMode() { try { // Set the server operation mode to DOWN unless in MM Server server = serverManager.getServer(); if (Server.OperationMode.MAINTENANCE != server.getOperationMode()) { topologyManager.updateServerMode(LookupUtil.getSubjectManager().getOverlord(), new Integer[] { server.getId() }, OperationMode.DOWN); } } catch (Throwable t) { // only show ugly stack traces if the user runs the server in debug mode if (log.isDebugEnabled()) { log.warn("Could not update this server's OperationMode to DOWN in the database", t); } else { log.warn("Could not update this server's OperationMode to DOWN in the database: " + t.getMessage()); } } } private void stopEmbeddedDatabase() { if (!isEmbedded()) { // only perform shutdown actions if we ABSOLUTELY know for sure this is the embedded database return; } Connection connection = null; Statement statement = null; try { connection = dataSource.getConnection(); statement = connection.createStatement(); statement.execute("shutdown"); log.info("Embedded database closed cleanly"); } catch (SQLException sqle) { if (sqle.getMessage().toLowerCase().indexOf("database is already closed") != -1) { log.warn("Database is already shut down, can not perform graceful service shutdown"); return; } else { // only show ugly stack traces if the user runs the server in debug mode if (log.isDebugEnabled()) { log.warn("Could not shut down the embedded database cleanly", sqle); } else { log.warn("Could not shut down the embedded database cleanly: " + sqle.getMessage()); } } } finally { JDBCUtil.safeClose(connection, statement, null); } } private boolean isEmbedded() { String identity = System.getProperty(RHQ_DB_TYPE_MAPPING_PROPERTY, ""); if (identity.equals("")) { log.error("Could not determine datatype base; is the " + RHQ_DB_TYPE_MAPPING_PROPERTY + " property set in rhq-server.properties?"); } return identity.toLowerCase().indexOf("h2") != -1; } }