From 714e93fc789f81ebc00f8fcc0269c5b03a3b1ee0 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Tue, 25 Apr 2017 18:20:49 +0200
Subject: [PATCH] [TAP] Improve the abortion of queries, particularly during
 the UPLOAD phase. Now, it is recommended to throw a DBCancelledException from
 any DBConnection long processing. It is already done for the upload of a
 table, the execution of an ADQL query and the setting of a whole TAP_SCHEMA.
 The flag JDBCConnection#cancelled has now a bit different meaning: it is set
 even if the Statement.cancel() fails so that any JDBCConnection function can
 see that a cancellation has been requested.

---
 src/tap/ADQLExecutor.java            |  38 +++--
 src/tap/db/DBCancelledException.java |  59 +++++++
 src/tap/db/DBConnection.java         |  15 +-
 src/tap/db/JDBCConnection.java       | 243 ++++++++++++++++++---------
 4 files changed, 260 insertions(+), 95 deletions(-)
 create mode 100644 src/tap/db/DBCancelledException.java

diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java
index 2be9b57..c1327eb 100644
--- a/src/tap/ADQLExecutor.java
+++ b/src/tap/ADQLExecutor.java
@@ -16,7 +16,7 @@ package tap;
  * You should have received a copy of the GNU Lesser General Public License
  * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -31,6 +31,7 @@ import adql.parser.ParseException;
 import adql.query.ADQLQuery;
 import tap.data.DataReadException;
 import tap.data.TableIterator;
+import tap.db.DBCancelledException;
 import tap.db.DBConnection;
 import tap.db.DBException;
 import tap.formatter.OutputFormat;
@@ -104,7 +105,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.1 (04/2016)
+ * @version 2.1 (04/2017)
  */
 public class ADQLExecutor {
 
@@ -267,7 +268,7 @@ public class ADQLExecutor {
 	 * @since 2.1
 	 */
 	public final void cancelQuery(){
-		if (dbConn != null && progression == ExecutionProgression.EXECUTING_ADQL)
+		if (dbConn != null && (progression == ExecutionProgression.EXECUTING_ADQL || progression == ExecutionProgression.UPLOADING))
 			dbConn.cancel(true);
 	}
 
@@ -382,6 +383,8 @@ public class ADQLExecutor {
 			endStep();
 
 			if (queryResult == null || thread.isInterrupted())
+				/* Note: 'queryResult == null' is for former version of the library
+				 *       ; now, a DBCancelledException should be thrown instead */
 				throw new InterruptedException();
 
 			// 4. WRITE RESULT:
@@ -400,6 +403,9 @@ public class ADQLExecutor {
 			logger.logTAP(LogLevel.INFO, report, "END_EXEC", "ADQL query execution finished.", null);
 
 			return report;
+
+		}catch(DBCancelledException dce){
+			throw new InterruptedException();
 		}finally{
 			// Close the result if any:
 			if (queryResult != null){
@@ -570,15 +576,15 @@ public class ADQLExecutor {
 	 * 
 	 * @param adql	The object representation of the ADQL query to execute.
 	 * 
-	 * @return	The result of the query,
-	 *        	or NULL if the query execution has failed.
+	 * @return	The result of the query.
 	 * 
 	 * @throws InterruptedException	If the thread has been interrupted.
+	 * @throws DBCancelledException	If the inner DB connection has been canceled.
 	 * @throws TAPException			If the {@link DBConnection} has failed to deal with the given ADQL query.
 	 * 
 	 * @see DBConnection#executeQuery(ADQLQuery)
 	 */
-	protected TableIterator executeADQL(final ADQLQuery adql) throws InterruptedException, TAPException{
+	protected TableIterator executeADQL(final ADQLQuery adql) throws InterruptedException, DBCancelledException, TAPException{
 		// Log the start of execution:
 		logger.logTAP(LogLevel.INFO, report, "START_DB_EXECUTION", "ADQL query: " + adql.toADQL().replaceAll("(\t|\r?\n)+", " "), null);
 
@@ -590,16 +596,22 @@ public class ADQLExecutor {
 				dbConn.setFetchSize(service.getFetchSize()[0]);
 		}
 
-		// Execute the ADQL query:
-		TableIterator result = dbConn.executeQuery(adql);
+		try{
+			// Execute the ADQL query:
+			TableIterator result = dbConn.executeQuery(adql);
 
-		// Log the success or failure:
-		if (result == null)
-			logger.logTAP(LogLevel.INFO, report, "END_DB_EXECUTION", "Query execution aborted after " + (System.currentTimeMillis() - startStep) + "ms!", null);
-		else
+			// If NULL, in a former version of the library, it means the query execution has been aborted:
+			if (result == null)
+				throw new DBCancelledException();
+
+			// Log the success:
 			logger.logTAP(LogLevel.INFO, report, "END_DB_EXECUTION", "Query successfully executed in " + (System.currentTimeMillis() - startStep) + "ms!", null);
 
-		return result;
+			return result;
+		}catch(DBCancelledException dce){
+			logger.logTAP(LogLevel.INFO, report, "END_DB_EXECUTION", "Query execution aborted after " + (System.currentTimeMillis() - startStep) + "ms!", null);
+			throw dce;
+		}
 	}
 
 	/**
diff --git a/src/tap/db/DBCancelledException.java b/src/tap/db/DBCancelledException.java
new file mode 100644
index 0000000..de654b4
--- /dev/null
+++ b/src/tap/db/DBCancelledException.java
@@ -0,0 +1,59 @@
+package tap.db;
+
+/*
+ * This file is part of TAPLibrary.
+ * 
+ * TAPLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * TAPLibrary 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 Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Copyright 2017 - Astronomisches Rechen Institut (ARI)
+ */
+
+/**
+ * Exception thrown when a processing of a {@link DBConnection} is cancelled.
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 2.1 (04/2017)
+ */
+public class DBCancelledException extends DBException {
+	private static final long serialVersionUID = 1L;
+
+	public DBCancelledException(){
+		super("DB interaction cancelled!");
+	}
+
+	public DBCancelledException(String message){
+		super(message);
+	}
+
+	public DBCancelledException(String message, String query){
+		super(message, query);
+	}
+
+	public DBCancelledException(Throwable cause){
+		super(cause);
+	}
+
+	public DBCancelledException(Throwable cause, String query){
+		super(cause, query);
+	}
+
+	public DBCancelledException(String message, Throwable cause){
+		super(message, cause);
+	}
+
+	public DBCancelledException(String message, Throwable cause, String query){
+		super(message, cause, query);
+	}
+
+}
diff --git a/src/tap/db/DBConnection.java b/src/tap/db/DBConnection.java
index d46e98f..658aa2d 100644
--- a/src/tap/db/DBConnection.java
+++ b/src/tap/db/DBConnection.java
@@ -45,7 +45,7 @@ import tap.metadata.TAPTable;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.1 (03/2017)
+ * @version 2.1 (04/2017)
  */
 public interface DBConnection {
 
@@ -172,11 +172,12 @@ public interface DBConnection {
 	 * 
 	 * @param metadata			List of all schemas, tables, columns and foreign keys to insert in the TAP_SCHEMA.
 	 * 
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing.
 	 * @throws DBException			If any error occurs while updating the database.
 	 * 
 	 * @since 2.0
 	 */
-	public void setTAPSchema(final TAPMetadata metadata) throws DBException;
+	public void setTAPSchema(final TAPMetadata metadata) throws DBCancelledException, DBException;
 
 	/**
 	 * Add the defined and given table inside the TAP_UPLOAD schema.
@@ -192,12 +193,13 @@ public interface DBConnection {
 	 * 
 	 * @return			<i>true</i> if the given table has been successfully added, <i>false</i> otherwise.
 	 * 
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing.
 	 * @throws DBException			If any error occurs while adding the table.
 	 * @throws DataReadException	If any error occurs while reading the given data (particularly if any limit - in byte or row - set in the TableIterator is reached).
 	 * 
 	 * @since 2.0
 	 */
-	public boolean addUploadedTable(final TAPTable tableDef, final TableIterator data) throws DBException, DataReadException;
+	public boolean addUploadedTable(final TAPTable tableDef, final TableIterator data) throws DBCancelledException, DBException, DataReadException;
 
 	/**
 	 * <p>Drop the specified uploaded table from the database.
@@ -237,16 +239,17 @@ public interface DBConnection {
 	 * 
 	 * @param adqlQuery	ADQL query to execute.
 	 * 
-	 * @return	The table result or NULL if the query has been aborted.
+	 * @return	The table result.
 	 * 
-	 * @throws DBException	If any errors occurs while executing the query.
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called (i.e. query aborted) during the processing.
+	 * @throws DBException			If any errors occurs while executing the query.
 	 * 
 	 * @since 2.0
 	 * 
 	 * @see #endQuery()
 	 * @see TableIterator#close()
 	 */
-	public TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException;
+	public TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBCancelledException, DBException;
 
 	/**
 	 * <p>Set the number of rows to fetch before searching/getting the following.
diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java
index a10a41e..bd2d519 100644
--- a/src/tap/db/JDBCConnection.java
+++ b/src/tap/db/JDBCConnection.java
@@ -90,7 +90,10 @@ import uws.service.log.UWSLog.LogLevel;
  * 
  * <p>
  * 	To cancel a query execution the function {@link #cancel(boolean)} must be called. No error is returned by this function in case
- * 	no query is currently executing.
+ * 	no query is currently executing. When called, the flag {@link #isCancelled()} is set to <code>true</code>. Any potentially long
+ * 	running function is checking this flag and may then stop immediately by throwing a {@link DBCancelledException} as soon as
+ * 	the flag turns <code>true</code>. It should be the case for {@link #addUploadedTable(TAPTable, TableIterator)},
+ * 	{@link #executeQuery(ADQLQuery)} and {@link #setTAPSchema(TAPMetadata)}.
  * </p>
  * 
  * 
@@ -177,7 +180,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </i></p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.1 (03/2017)
+ * @version 2.1 (04/2017)
  * @since 2.0
  */
 public class JDBCConnection implements DBConnection {
@@ -210,7 +213,7 @@ public class JDBCConnection implements DBConnection {
 	protected Statement stmt = null;
 
 	/**
-	 * <p>It <code>true</code>, this flag indicates that the function {@link #cancel(boolean)} has been called successfully.</p>
+	 * <p>If <code>true</code>, this flag indicates that the function {@link #cancel(boolean)} has been called at least once.</p>
 	 * 
 	 * <p>{@link #cancel(boolean)} sets this flag to <code>true</code>.</p>
 	 * <p>
@@ -218,10 +221,6 @@ public class JDBCConnection implements DBConnection {
 	 * 	by calling the function {@link #resetCancel()}.
 	 * </p>
 	 * <p>
-	 * 	This flag is particularly useful for debugging: when an exception is detected inside a function executing a query,
-	 * 	this flag is used to know whether the exception should be ignored for logging (if <code>true</code>) or not.
-	 * </p>
-	 * <p>
 	 * 	Any access (write AND read) to this flag MUST be synchronized on it using one of the following functions:
 	 * 	{@link #cancel(boolean)}, {@link #resetCancel()} and {@link #isCancelled()}.
 	 * </p>
@@ -525,14 +524,19 @@ public class JDBCConnection implements DBConnection {
 	 * <p>Cancel (and rollback when possible) the currently running query of this {@link JDBCConnection} instance.</p>
 	 * 
 	 * <p><b>Important note:</b>
-	 * 	This function is effective only if the JDBC driver and DBMS both support
-	 * 	this operation.
+	 * 	This function tries canceling the current JDBC statement. This can work only if the JDBC driver and
+	 * 	the DBMS both support this operation. If the statement cancellation fails, the flag {@link #supportsCancel}
+	 * 	is set to <code>false</code> so that any subsequent call of this function for this instance of
+	 * 	{@link JDBCConnection} does not try any other cancellation attempt. <b>HOWEVER</b> the rollback will
+	 * 	still continue to be performed if the parameter <code>rollback</code> is set to <code>true</code>.
 	 * </p>
+	 * 
 	 * <p>
-	 * 	If a call of this function fails the flag {@link #supportsCancel} is set to false
-	 * 	so that any subsequent call of this function for this instance of {@link JDBCConnection}
-	 * 	does not try any other cancellation attempt. <b>HOWEVER</b> the rollback will still continue
-	 * 	to be performed if the parameter <code>rollback</code> is set to <code>true</code>.
+	 * 	In any case, this function sets anyway the flag {@link #isCancelled()} to <code>true</code> so that after
+	 * 	a DB processing this {@link DBConnection} can interrupt immediately any potentially long running functions
+	 * 	(i.e. {@link #addUploadedTable(TAPTable, TableIterator)}, {@link #executeQuery(ADQLQuery)} and
+	 * 	{@link #setTAPSchema(TAPMetadata)}). When these functions realize this flag is set, they immediately stop
+	 * 	by throwing a {@link DBCancelledException}.
 	 * </p>
 	 * 
 	 * <p><i>Note 1:
@@ -542,12 +546,6 @@ public class JDBCConnection implements DBConnection {
 	 * </i></p>
 	 * 
 	 * <p><i>Note 2:
-	 * 	In case of cancellation success, the flag {@link #cancelled} is set to <code>true</code>.
-	 * 	Thus, the function executing a query can know that if any SQL exception is thrown, it will be due to the cancellation and
-	 * 	should not be then considered as a real error (=> exception not logged but anyway propagated in order to stop any processing).
-	 * </i></p></p>
-	 * 
-	 * <p><i>Note 3:
 	 * 	This function is synchronized on the {@link #cancelled} flag.
 	 * 	Thus, it may block until another synchronized block on this same flag is finished.
 	 * </i></p>
@@ -563,9 +561,10 @@ public class JDBCConnection implements DBConnection {
 	@Override
 	public final void cancel(final boolean rollback){
 		synchronized(cancelled){
-			cancelled = cancel(stmt, rollback);
+			cancelled = true;
+			boolean effectivelyCancelled = cancel(stmt, rollback);
 			// Log the success of the cancellation:
-			if (cancelled && logger != null)
+			if (effectivelyCancelled && logger != null)
 				logger.logDB(LogLevel.INFO, this, "CANCEL", "Query execution successfully stopped!", null);
 		}
 	}
@@ -574,14 +573,11 @@ public class JDBCConnection implements DBConnection {
 	 * <p>Cancel (and rollback when asked and if possible) the given statement.</p>
 	 * 
 	 * <p><b>Important note:</b>
-	 * 	This function is effective only if the JDBC driver and DBMS both support
-	 * 	this operation.
-	 * </p>
-	 * <p>
-	 * 	If a call of this function fails the flag {@link #supportsCancel} is set to false
-	 * 	so that any subsequent call of this function for this instance of {@link JDBCConnection}
-	 * 	does not try any other cancellation attempt. <b>HOWEVER</b> the rollback will still continue
-	 * 	to be performed if the parameter <code>rollback</code> is set to <code>true</code>.
+	 * 	This function tries canceling the current JDBC statement. This can work only if the JDBC driver and
+	 * 	the DBMS both support this operation. If the statement cancellation fails, the flag {@link #supportsCancel}
+	 * 	is set to <code>false</code> so that any subsequent call of this function for this instance of
+	 * 	{@link JDBCConnection} does not try any other cancellation attempt. <b>HOWEVER</b> the rollback will
+	 * 	still continue to be performed if the parameter <code>rollback</code> is set to <code>true</code>.
 	 * </p>
 	 * 
 	 * <p><i>Note:
@@ -624,7 +620,7 @@ public class JDBCConnection implements DBConnection {
 		// Whatever happens, rollback all executed operations (only if rollback=true and if in a transaction ; that's to say if AutoCommit = false):
 		finally{
 			if (rollback && supportsTransaction)
-				rollback();
+				rollback((stmt != null && stmt == this.stmt));
 		}
 	}
 
@@ -704,8 +700,14 @@ public class JDBCConnection implements DBConnection {
 				}
 			}
 
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+
+			// Get a statement:
 			getStatement();
 
+			// Adjust the fetching size of this statement:
 			if (supportsFetchSize){
 				try{
 					stmt.setFetchSize(fetchSize);
@@ -723,9 +725,9 @@ public class JDBCConnection implements DBConnection {
 				logger.logDB(LogLevel.INFO, this, "EXECUTE", "SQL query: " + sql.replaceAll("(\t|\r?\n)+", " "), null);
 			result = stmt.executeQuery(sql);
 
-			// If the query has been aborted and no result is provided, return immediately:
-			if (cancelled)
-				return null;
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
 
 			// 4. Return the result through a TableIterator object:
 			if (logger != null)
@@ -737,16 +739,13 @@ public class JDBCConnection implements DBConnection {
 			close(result);
 			// End properly the query:
 			endQuery();
-			// Propagate the exception with an appropriate error message:
-			if (ex instanceof SQLException){
-				/* ...except if the query has been aborted:
-				 * then, it is normal to receive an SQLException
-				 * and NULL should be returned: */
-				if (isCancelled())
-					return null;
-				else
-					throw new DBException("Unexpected error while executing a SQL query: " + ex.getMessage(), ex);
-			}else if (ex instanceof TranslationException)
+			// Propagate the exception if it is just about the cancellation:
+			if (ex instanceof DBCancelledException)
+				throw (DBCancelledException)ex;
+			// Otherwise propagate the exception with an appropriate error message:
+			else if (ex instanceof SQLException)
+				throw new DBException("Unexpected error while executing a SQL query: " + ex.getMessage(), ex);
+			else if (ex instanceof TranslationException)
 				throw new DBException("Unexpected error while translating ADQL into SQL: " + ex.getMessage(), ex);
 			else if (ex instanceof DataReadException)
 				throw new DBException("Impossible to read the query result, because: " + ex.getMessage(), ex);
@@ -1388,7 +1387,7 @@ public class JDBCConnection implements DBConnection {
 	 * @see tap.db.DBConnection#setTAPSchema(tap.metadata.TAPMetadata)
 	 */
 	@Override
-	public synchronized void setTAPSchema(final TAPMetadata metadata) throws DBException{
+	public synchronized void setTAPSchema(final TAPMetadata metadata) throws DBCancelledException, DBException{
 		// Starting of new query execution => disable the cancel flag:
 		resetCancel();
 
@@ -1406,11 +1405,18 @@ public class JDBCConnection implements DBConnection {
 				logger.logDB(LogLevel.INFO, this, "CLEAN_TAP_SCHEMA", "Cleaning TAP_SCHEMA.", null);
 			resetTAPSchema(stmt, stdTables);
 
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+
 			// 2. Create all standard TAP tables:
 			if (logger != null)
 				logger.logDB(LogLevel.INFO, this, "CREATE_TAP_SCHEMA", "Creating TAP_SCHEMA tables.", null);
-			for(TAPTable table : stdTables)
+			for(TAPTable table : stdTables){
 				createTAPSchemaTable(table, stmt);
+				if (isCancelled())
+					throw new DBCancelledException();
+			}
 
 			// C. FILL THE NEW TABLE USING THE GIVEN DATA ITERATOR:
 			if (logger != null)
@@ -1424,11 +1430,17 @@ public class JDBCConnection implements DBConnection {
 				createTAPTableIndexes(table, stmt);
 
 			commit();
+		}catch(DBCancelledException dce){
+			rollback();
+			throw dce;
 		}catch(SQLException se){
 			if (!isCancelled() && logger != null)
 				logger.logDB(LogLevel.ERROR, this, "CREATE_TAP_SCHEMA", "Impossible to SET TAP_SCHEMA in DB!", se);
 			rollback();
-			throw new DBException("Impossible to SET TAP_SCHEMA in DB!", se);
+			if (isCancelled())
+				throw new DBCancelledException();
+			else
+				throw new DBException("Impossible to SET TAP_SCHEMA in DB!", se);
 		}finally{
 			closeStatement();
 			endTransaction();
@@ -1719,10 +1731,11 @@ public class JDBCConnection implements DBConnection {
 	 * @param table	Table whose indexes must be created here.
 	 * @param stmt	Statement to use in order to interact with the database.
 	 * 
-	 * @throws DBException	If the given table is not a standard TAP_SCHEMA table.
-	 * @throws SQLException	If any error occurs while querying or updating the database.
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing,
+	 * @throws DBException			If the given table is not a standard TAP_SCHEMA table.
+	 * @throws SQLException			If any error occurs while querying or updating the database.
 	 */
-	protected void createTAPTableIndexes(final TAPTable table, final Statement stmt) throws DBException, SQLException{
+	protected void createTAPTableIndexes(final TAPTable table, final Statement stmt) throws DBCancelledException, DBException, SQLException{
 		// 1. Ensure the given table is really a TAP_SCHEMA table (according to the ADQL names):
 		if (!table.getADQLSchemaName().equalsIgnoreCase(STDSchema.TAPSCHEMA.label) || TAPMetadata.resolveStdTable(table.getADQLName()) == null)
 			throw new DBException("Forbidden index creation: " + table + " is not a standard table of TAP_SCHEMA!");
@@ -1739,6 +1752,9 @@ public class JDBCConnection implements DBConnection {
 			// Create an index only for columns that have the 'indexed' flag:
 			if (col.isIndexed() && !isPartOfPrimaryKey(col.getADQLName()))
 				stmt.executeUpdate("CREATE INDEX " + indexNamePrefix + col.getADQLName() + " ON " + dbTableName + "(" + translator.getColumnName(col) + ")");
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
 		}
 	}
 
@@ -1770,10 +1786,11 @@ public class JDBCConnection implements DBConnection {
 	 * 
 	 * @param meta	All schemas and tables to list inside the TAP_SCHEMA tables.
 	 * 
-	 * @throws DBException	If rows can not be inserted because the SQL update query has failed.
-	 * @throws SQLException	If any other SQL exception occurs.
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing,
+	 * @throws DBException			If rows can not be inserted because the SQL update query has failed.
+	 * @throws SQLException			If any other SQL exception occurs.
 	 */
-	protected void fillTAPSchema(final TAPMetadata meta) throws SQLException, DBException{
+	protected void fillTAPSchema(final TAPMetadata meta) throws SQLException, DBCancelledException, DBException{
 		TAPTable metaTable;
 
 		// 1. Fill SCHEMAS:
@@ -1809,10 +1826,11 @@ public class JDBCConnection implements DBConnection {
 	 * 
 	 * @return	Iterator over the full list of all tables (whatever is their schema).
 	 * 
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing,
 	 * @throws DBException	If rows can not be inserted because the SQL update query has failed.
 	 * @throws SQLException	If any other SQL exception occurs.
 	 */
-	private Iterator<TAPTable> fillSchemas(final TAPTable metaTable, final Iterator<TAPSchema> itSchemas) throws SQLException, DBException{
+	private Iterator<TAPTable> fillSchemas(final TAPTable metaTable, final Iterator<TAPSchema> itSchemas) throws SQLException, DBCancelledException, DBException{
 		List<TAPTable> allTables = new ArrayList<TAPTable>();
 
 		// Build the SQL update query:
@@ -1847,9 +1865,18 @@ public class JDBCConnection implements DBConnection {
 				stmt.setString(3, schema.getUtype());
 				if (supportsSchema)
 					stmt.setString(4, (schema.getDBName() == null || schema.getDBName().equals(schema.getADQLName())) ? null : schema.getDBName());
-				executeUpdate(stmt, nbRows);
+
+				// If the query has been aborted, return immediately:
+				if (isCancelled())
+					throw new DBCancelledException();
+				else
+					executeUpdate(stmt, nbRows);
 			}
-			executeBatchUpdates(stmt, nbRows);
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+			else
+				executeBatchUpdates(stmt, nbRows);
 		}finally{
 			close(stmt);
 		}
@@ -1870,10 +1897,11 @@ public class JDBCConnection implements DBConnection {
 	 * 
 	 * @return	Iterator over the full list of all columns (whatever is their table).
 	 * 
-	 * @throws DBException	If rows can not be inserted because the SQL update query has failed.
-	 * @throws SQLException	If any other SQL exception occurs.
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing,
+	 * @throws DBException			If rows can not be inserted because the SQL update query has failed.
+	 * @throws SQLException			If any other SQL exception occurs.
 	 */
-	private Iterator<TAPColumn> fillTables(final TAPTable metaTable, final Iterator<TAPTable> itTables) throws SQLException, DBException{
+	private Iterator<TAPColumn> fillTables(final TAPTable metaTable, final Iterator<TAPTable> itTables) throws SQLException, DBCancelledException, DBException{
 		List<TAPColumn> allColumns = new ArrayList<TAPColumn>();
 
 		// Build the SQL update query:
@@ -1913,9 +1941,18 @@ public class JDBCConnection implements DBConnection {
 				stmt.setString(5, table.getUtype());
 				stmt.setInt(6, table.getIndex());
 				stmt.setString(7, (table.getDBName() == null || table.getDBName().equals(table.getADQLName())) ? null : table.getDBName());
-				executeUpdate(stmt, nbRows);
+
+				// If the query has been aborted, return immediately:
+				if (isCancelled())
+					throw new DBCancelledException();
+				else
+					executeUpdate(stmt, nbRows);
 			}
-			executeBatchUpdates(stmt, nbRows);
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+			else
+				executeBatchUpdates(stmt, nbRows);
 		}finally{
 			close(stmt);
 		}
@@ -1936,10 +1973,11 @@ public class JDBCConnection implements DBConnection {
 	 * 
 	 * @return	Iterator over the full list of all foreign keys.
 	 * 
-	 * @throws DBException	If rows can not be inserted because the SQL update query has failed.
-	 * @throws SQLException	If any other SQL exception occurs.
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing,
+	 * @throws DBException			If rows can not be inserted because the SQL update query has failed.
+	 * @throws SQLException			If any other SQL exception occurs.
 	 */
-	private Iterator<TAPForeignKey> fillColumns(final TAPTable metaTable, final Iterator<TAPColumn> itColumns) throws SQLException, DBException{
+	private Iterator<TAPForeignKey> fillColumns(final TAPTable metaTable, final Iterator<TAPColumn> itColumns) throws SQLException, DBCancelledException, DBException{
 		List<TAPForeignKey> allKeys = new ArrayList<TAPForeignKey>();
 
 		// Build the SQL update query:
@@ -1993,9 +2031,18 @@ public class JDBCConnection implements DBConnection {
 				stmt.setInt(12, col.isStd() ? 1 : 0);
 				stmt.setInt(13, col.getIndex());
 				stmt.setString(14, (col.getDBName() == null || col.getDBName().equals(col.getADQLName())) ? null : col.getDBName());
-				executeUpdate(stmt, nbRows);
+
+				// If the query has been aborted, return immediately:
+				if (isCancelled())
+					throw new DBCancelledException();
+				else
+					executeUpdate(stmt, nbRows);
 			}
-			executeBatchUpdates(stmt, nbRows);
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+			else
+				executeBatchUpdates(stmt, nbRows);
 		}finally{
 			close(stmt);
 		}
@@ -2015,10 +2062,11 @@ public class JDBCConnection implements DBConnection {
 	 * @param metaKeyColumns	Description of TAP_SCHEMA.key_columns.
 	 * @param itKeys			Iterator over the list of foreign keys.
 	 * 
-	 * @throws DBException	If rows can not be inserted because the SQL update query has failed.
-	 * @throws SQLException	If any other SQL exception occurs.
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing,
+	 * @throws DBException			If rows can not be inserted because the SQL update query has failed.
+	 * @throws SQLException			If any other SQL exception occurs.
 	 */
-	private void fillKeys(final TAPTable metaKeys, final TAPTable metaKeyColumns, final Iterator<TAPForeignKey> itKeys) throws SQLException, DBException{
+	private void fillKeys(final TAPTable metaKeys, final TAPTable metaKeyColumns, final Iterator<TAPForeignKey> itKeys) throws SQLException, DBCancelledException, DBException{
 		// Build the SQL update query for KEYS:
 		StringBuffer sqlKeys = new StringBuffer("INSERT INTO ");
 		sqlKeys.append(translator.getTableName(metaKeys, supportsSchema)).append(" (");
@@ -2063,7 +2111,12 @@ public class JDBCConnection implements DBConnection {
 					stmtKeys.setString(3, key.getTargetTable().getADQLName());
 				stmtKeys.setString(4, key.getDescription());
 				stmtKeys.setString(5, key.getUtype());
-				executeUpdate(stmtKeys, nbKeys);
+
+				// If the query has been aborted, return immediately:
+				if (isCancelled())
+					throw new DBCancelledException();
+				else
+					executeUpdate(stmtKeys, nbKeys);
 
 				// add the key columns into KEY_COLUMNS:
 				Iterator<Map.Entry<String,String>> itAssoc = key.iterator();
@@ -2073,12 +2126,22 @@ public class JDBCConnection implements DBConnection {
 					stmtKeyCols.setString(1, key.getKeyId());
 					stmtKeyCols.setString(2, assoc.getKey());
 					stmtKeyCols.setString(3, assoc.getValue());
-					executeUpdate(stmtKeyCols, nbKeyColumns);
+
+					// If the query has been aborted, return immediately:
+					if (isCancelled())
+						throw new DBCancelledException();
+					else
+						executeUpdate(stmtKeyCols, nbKeyColumns);
 				}
 			}
 
-			executeBatchUpdates(stmtKeys, nbKeys);
-			executeBatchUpdates(stmtKeyCols, nbKeyColumns);
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+			else{
+				executeBatchUpdates(stmtKeys, nbKeys);
+				executeBatchUpdates(stmtKeyCols, nbKeyColumns);
+			}
 		}finally{
 			close(stmtKeys);
 			close(stmtKeyCols);
@@ -2143,6 +2206,10 @@ public class JDBCConnection implements DBConnection {
 				throw de;
 			}
 
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+
 			// 2. Create the table:
 			// ...build the SQL query:
 			StringBuffer sqlBuf = new StringBuffer("CREATE TABLE ");
@@ -2162,8 +2229,14 @@ public class JDBCConnection implements DBConnection {
 			// ...execute the update query:
 			stmt.executeUpdate(sqlBuf.toString());
 
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+
 			// 3. Fill the table:
 			int nbUploadedRows = fillUploadedTable(tableDef, data);
+			if (isCancelled())
+				throw new DBCancelledException();
 
 			// Commit the transaction:
 			commit();
@@ -2181,6 +2254,8 @@ public class JDBCConnection implements DBConnection {
 			throw new DBException("Impossible to create the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
 		}catch(DBException de){
 			rollback();
+			if (logger != null && (de instanceof DBCancelledException || isCancelled()))
+				logger.logDB(LogLevel.INFO, this, "ADD_UPLOAD_TABLE", "Upload of the table \"" + tableDef.getADQLName() + "\" (in DB: " + translator.getTableName(tableDef, supportsSchema) + ") canceled!", null);
 			throw de;
 		}catch(DataReadException dre){
 			rollback();
@@ -2208,11 +2283,12 @@ public class JDBCConnection implements DBConnection {
 	 * 
 	 * @return	Number of inserted rows.
 	 * 
+	 * @throws DBCancelledException	If {@link #cancel(boolean)} has been called during the processing,
 	 * @throws DBException			If rows can not be inserted because the SQL update query has failed.
 	 * @throws SQLException			If any other SQL exception occurs.
 	 * @throws DataReadException	If there is any error while reading the data from the given {@link TableIterator} (and particularly if a limit - in byte or row - has been reached).
 	 */
-	protected int fillUploadedTable(final TAPTable metaTable, final TableIterator data) throws SQLException, DBException, DataReadException{
+	protected int fillUploadedTable(final TAPTable metaTable, final TableIterator data) throws SQLException, DBCancelledException, DBException, DataReadException{
 		// 1. Build the SQL update query:
 		StringBuffer sql = new StringBuffer("INSERT INTO ");
 		StringBuffer varParam = new StringBuffer();
@@ -2281,11 +2357,26 @@ public class JDBCConnection implements DBConnection {
 						else if ((dbms == null || dbms.equalsIgnoreCase(DBMS_POSTGRES)) && val instanceof Character && (Character)val == 0x00)
 							val = null;
 					}
-					stmt.setObject(c++, val);
+
+					// If the query has been aborted, return immediately:
+					if (isCancelled())
+						throw new DBCancelledException();
+					else
+						stmt.setObject(c++, val);
 				}
-				executeUpdate(stmt, nbRows);
+
+				// If the query has been aborted, return immediately:
+				if (isCancelled())
+					throw new DBCancelledException();
+				else
+					executeUpdate(stmt, nbRows);
 			}
-			executeBatchUpdates(stmt, nbRows);
+
+			// If the query has been aborted, return immediately:
+			if (isCancelled())
+				throw new DBCancelledException();
+			else
+				executeBatchUpdates(stmt, nbRows);
 
 			return nbRows;
 
-- 
GitLab