/*
* (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.common.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import javax.sql.DataSource;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Helper for common JDBC-related operations.
*
* @since 7.3
*/
public class JDBCUtils {
private static final Log log = LogFactory.getLog(JDBCUtils.class);
/**
* Maximum number of times we retry a call if the server says it's overloaded.
*/
public static final int MAX_TRIES = 5;
/**
* Tries to do a JDBC call even when the server is overloaded.
* <p>
* Oracle has problems opening and closing many connections in a short time span (ORA-12516, ORA-12519). It seems to
* have something to do with how closed sessions are not immediately accounted for by Oracle's PMON (process
* monitor). When we get these errors, we retry a few times with exponential backoff.
*
* @param callable the callable
* @return the returned value
*/
public static <V> V callWithRetry(Callable<V> callable) throws SQLException {
for (int tryNo = 1;; tryNo++) {
try {
return callable.call();
} catch (SQLException e) {
if (tryNo >= MAX_TRIES) {
throw e;
}
int errorCode = e.getErrorCode();
if (errorCode != 12516 && errorCode != 12519) {
throw e;
}
// Listener refused the connection with the following error:
// ORA-12519, TNS:no appropriate service handler found
// ORA-12516, TNS:listener could not find available handler with matching protocol stack
if (log.isDebugEnabled()) {
log.debug(String.format("Connections open too fast, retrying in %ds: %s", Integer.valueOf(tryNo),
e.getMessage().replace("\n", " ")));
}
try {
Thread.sleep(1000 * tryNo);
} catch (InterruptedException ie) { // deals with interrupt below
throw ExceptionUtils.runtimeException(e);
}
} catch (Exception e) { // deals with interrupt below
throw ExceptionUtils.runtimeException(e);
}
}
}
/**
* Tries to acquire a {@link Connection} through the {@link DriverManager} even when the server is overloaded.
*
* @param url a database url of the form <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
* @param user the database user on whose behalf the connection is being made
* @param password the user's password
* @return a connection to the URL
*/
public static Connection getConnection(final String url, final String user, final String password)
throws SQLException {
return callWithRetry(new Callable<Connection>() {
@Override
public Connection call() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
});
}
/**
* Tries to acquire a {@link Connection} through a {@link DataSource} even when the server is overloaded.
*
* @param dataSource the data source
* @return a connection to the data source
*/
public static Connection getConnection(final DataSource dataSource) throws SQLException {
return callWithRetry(new Callable<Connection>() {
@Override
public Connection call() throws SQLException {
return dataSource.getConnection();
}
});
}
/**
* Tries to acquire a {@link XAConnection} through a {@link XADataSource} even when the server is overloaded.
*
* @param xaDataSource the XA data source
* @return a XA connection to the XA data source
*/
public static XAConnection getXAConnection(final XADataSource xaDataSource) throws SQLException {
return callWithRetry(new Callable<XAConnection>() {
@Override
public XAConnection call() throws SQLException {
return xaDataSource.getXAConnection();
}
});
}
}