/* Copyright (c) 2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Justin Deoliveira (Boundless) - initial implementation
*/
package org.locationtech.geogig.storage.sqlite;
import static org.locationtech.geogig.storage.sqlite.XerialSQLiteModule.LOG;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayDeque;
import java.util.Deque;
import javax.sql.DataSource;
import com.google.common.base.Throwables;
/**
* Helper class for executing statements and queries against a JDBC connection.
* <p>
* Usage:
*
* <pre>
* <code>
* Connection cx = ...;
* Long count = new DbOp<Long>() {
* public Long run(Connection cx) {
* ResultSet rs = open(open(cx.createStatement()).executeQuery("SELECT count(*) FROM foo"));
* rs.next();
* return rs.getLong(1);
* }
* }.run(cx):
*
* </code>
* </pre>
*
* </p>
*
* @author Justin Deoliveira, Boundless
*
* @param <T> Operation return type.
*/
public abstract class DbOp<T> {
Deque<Object> open = new ArrayDeque<Object>();
/**
* Runs the op against a new connection provided by the data source.
* <p>
* The connection is closed after usage.
* </p>
*
* @param ds The data source to obtain connection from.
*/
public final T run(DataSource ds) {
try {
return run(open(ds.getConnection()));
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Runs the op.
*
* @param cx The connection to run the op against.
*
* @return The op result.
*/
public final T run(Connection cx) {
try {
try {
boolean auto = isAutoCommit();
if (!auto) {
cx.setAutoCommit(false);
}
try {
return doRun(cx);
} finally {
if (!auto) {
cx.setAutoCommit(true);
}
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
} finally {
close();
}
}
/**
* Tracks a resource to be closed in LIFO order.
*
* @param obj The object to be closed.
*
* @return The original object.
*/
protected <X> X open(X obj) {
open.push(obj);
return obj;
}
void close() {
while (!open.isEmpty()) {
Object obj = open.pop();
try {
if (obj instanceof ResultSet) {
((ResultSet) obj).close();
}
if (obj instanceof Statement) {
((Statement) obj).close();
}
if (obj instanceof Connection) {
((Connection) obj).close();
}
} catch (Exception e) {
LOG.debug("error closing object: " + obj, e);
}
}
}
/**
* Hook for sublcasses to run.
* <p>
* When creating statements and result sets from the connection ensure that {@link #open} is
* called in order to track the created object to be closed when the operation is complete.
* </p>
*
* @param cx The connection to run the op against.
*
* @return The op result, or <code>null</code> for opts that don't return a value.
*
*/
protected abstract T doRun(Connection cx) throws IOException, SQLException;
/**
* Subclass hook to determine if the operation runs within a transaction.
* <p>
* It is the responsibility of the subclass to either commit or rollback the transaction.
* </p>
*/
protected boolean isAutoCommit() {
return true;
}
}