001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.support;
018
019import java.sql.Connection;
020import java.sql.SQLException;
021import java.sql.Statement;
022import javax.sql.DataSource;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.beans.factory.InitializingBean;
028import org.springframework.jdbc.CannotGetJdbcConnectionException;
029
030/**
031 * Bean that checks if a database has already started up. To be referenced
032 * via "depends-on" from beans that depend on database startup, like a Hibernate
033 * SessionFactory or custom data access objects that access a DataSource directly.
034 *
035 * <p>Useful to defer application initialization until a database has started up.
036 * Particularly appropriate for waiting on a slowly starting Oracle database.
037 *
038 * @author Juergen Hoeller
039 * @since 18.12.2003
040 */
041public class DatabaseStartupValidator implements InitializingBean {
042
043        public static final int DEFAULT_INTERVAL = 1;
044
045        public static final int DEFAULT_TIMEOUT = 60;
046
047
048        protected final Log logger = LogFactory.getLog(getClass());
049
050        private DataSource dataSource;
051
052        private String validationQuery;
053
054        private int interval = DEFAULT_INTERVAL;
055
056        private int timeout = DEFAULT_TIMEOUT;
057
058
059        /**
060         * Set the DataSource to validate.
061         */
062        public void setDataSource(DataSource dataSource) {
063                this.dataSource = dataSource;
064        }
065
066        /**
067         * Set the SQL query string to use for validation.
068         */
069        public void setValidationQuery(String validationQuery) {
070                this.validationQuery = validationQuery;
071        }
072
073        /**
074         * Set the interval between validation runs (in seconds).
075         * Default is {@value #DEFAULT_INTERVAL}.
076         */
077        public void setInterval(int interval) {
078                this.interval = interval;
079        }
080
081        /**
082         * Set the timeout (in seconds) after which a fatal exception
083         * will be thrown. Default is {@value #DEFAULT_TIMEOUT}.
084         */
085        public void setTimeout(int timeout) {
086                this.timeout = timeout;
087        }
088
089
090        /**
091         * Check whether the validation query can be executed on a Connection
092         * from the specified DataSource, with the specified interval between
093         * checks, until the specified timeout.
094         */
095        @Override
096        public void afterPropertiesSet() {
097                DataSource dataSource = this.dataSource;
098                if (dataSource == null) {
099                        throw new IllegalArgumentException("Property 'dataSource' is required");
100                }
101                if (this.validationQuery == null) {
102                        throw new IllegalArgumentException("Property 'validationQuery' is required");
103                }
104
105                try {
106                        boolean validated = false;
107                        long beginTime = System.currentTimeMillis();
108                        long deadLine = beginTime + this.timeout * 1000;
109                        SQLException latestEx = null;
110
111                        while (!validated && System.currentTimeMillis() < deadLine) {
112                                Connection con = null;
113                                Statement stmt = null;
114                                try {
115                                        con = dataSource.getConnection();
116                                        stmt = con.createStatement();
117                                        stmt.execute(this.validationQuery);
118                                        validated = true;
119                                }
120                                catch (SQLException ex) {
121                                        latestEx = ex;
122                                        if (logger.isDebugEnabled()) {
123                                                logger.debug("Validation query [" + this.validationQuery + "] threw exception", ex);
124                                        }
125                                        if (logger.isWarnEnabled()) {
126                                                float rest = ((float) (deadLine - System.currentTimeMillis())) / 1000;
127                                                if (rest > this.interval) {
128                                                        logger.warn("Database has not started up yet - retrying in " + this.interval +
129                                                                        " seconds (timeout in " + rest + " seconds)");
130                                                }
131                                        }
132                                }
133                                finally {
134                                        JdbcUtils.closeStatement(stmt);
135                                        JdbcUtils.closeConnection(con);
136                                }
137
138                                if (!validated) {
139                                        Thread.sleep(this.interval * 1000);
140                                }
141                        }
142
143                        if (!validated) {
144                                throw new CannotGetJdbcConnectionException(
145                                                "Database has not started up within " + this.timeout + " seconds", latestEx);
146                        }
147
148                        if (logger.isInfoEnabled()) {
149                                float duration = ((float) (System.currentTimeMillis() - beginTime)) / 1000;
150                                logger.info("Database startup detected after " + duration + " seconds");
151                        }
152                }
153                catch (InterruptedException ex) {
154                        // Re-interrupt current thread, to allow other threads to react.
155                        Thread.currentThread().interrupt();
156                }
157        }
158
159}