/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import org.eclipse.ecr.core.storage.StorageException;
import org.eclipse.ecr.core.storage.sql.Model;
import org.eclipse.ecr.core.storage.sql.Mapper.Identification;
/**
* Holds a connection to a JDBC database.
*/
public class JDBCConnection {
/** The model used to do the mapping. */
protected final Model model;
/** The SQL information. */
protected final SQLInfo sqlInfo;
/** The xa datasource. */
protected final XADataSource xadatasource;
/** The xa pooled connection. */
private XAConnection xaconnection;
/** The actual connection. */
public Connection connection;
protected XAResource xaresource;
protected final JDBCConnectionPropagator connectionPropagator;
/** If there's a chance the connection may be closed. */
protected volatile boolean checkConnectionValid;
// for debug
private static final AtomicLong instanceCounter = new AtomicLong(0);
// for debug
private final long instanceNumber = instanceCounter.incrementAndGet();
// for debug
public final JDBCLogger logger = new JDBCLogger(
String.valueOf(instanceNumber));
/**
* Creates a new Mapper.
*
* @param model the model
* @param sqlInfo the sql info
* @param xadatasource the XA datasource to use to get connections
*/
public JDBCConnection(Model model, SQLInfo sqlInfo,
XADataSource xadatasource,
JDBCConnectionPropagator connectionPropagator)
throws StorageException {
this.model = model;
this.sqlInfo = sqlInfo;
this.xadatasource = xadatasource;
this.connectionPropagator = connectionPropagator;
connectionPropagator.addConnection(this);
open();
}
public Identification getIdentification() {
return new Identification(null, "" + instanceNumber);
}
protected void open() throws StorageException {
try {
xaconnection = xadatasource.getXAConnection();
connection = xaconnection.getConnection();
xaresource = xaconnection.getXAResource();
} catch (SQLException e) {
throw new StorageException(e);
}
}
public void close() {
if (connection != null) {
try {
connection.close();
} catch (Exception e) {
// ignore, including UndeclaredThrowableException
}
}
if (xaconnection != null) {
try {
xaconnection.close();
} catch (SQLException e) {
// ignore
}
}
xaconnection = null;
connection = null;
xaresource = null;
}
/**
* Opens a new connection if the previous ones was broken or timed out.
*/
protected void resetConnection() throws StorageException {
logger.error("Resetting connection");
close();
open();
// we had to reset a connection; notify all the others that they
// should check their validity proactively
connectionPropagator.checkConnectionValid(this);
}
/**
* Checks that the connection is valid, and tries to reset it if not.
*/
protected void checkConnectionValid() throws StorageException {
if (checkConnectionValid) {
doCheckConnectionValid();
// only if there was no exception set the flag to false
checkConnectionValid = false;
}
}
protected void doCheckConnectionValid() throws StorageException {
Statement st = null;
try {
st = connection.createStatement();
st.execute(sqlInfo.dialect.getValidationQuery());
} catch (Exception e) {
if (sqlInfo.dialect.isConnectionClosedException(e)) {
resetConnection();
} else {
throw new StorageException(e);
}
} finally {
if (st != null) {
try {
st.close();
} catch (Exception e) {
// ignore
}
}
}
}
/**
* Checks the SQL error we got and determine if the low level connection has
* to be reset.
* <p>
* Called with a generic Exception and not just SQLException because the
* PostgreSQL JDBC driver sometimes fails to unwrap properly some
* InvocationTargetException / UndeclaredThrowableException.
*/
protected void checkConnectionReset(Throwable t) throws StorageException {
if (sqlInfo.dialect.isConnectionClosedException(t)) {
resetConnection();
}
}
/**
* Checks the XA error we got and determine if the low level connection has
* to be reset.
*/
protected void checkConnectionReset(XAException e) {
if (sqlInfo.dialect.isConnectionClosedException(e)) {
try {
resetConnection();
} catch (StorageException ee) {
// swallow, exception already thrown by caller
}
}
}
protected void closeStatement(Statement s) throws SQLException {
try {
s.close();
} catch (IllegalArgumentException e) {
// ignore
// http://bugs.mysql.com/35489 with JDBC 4 and driver <= 5.1.6
}
}
}