Skip to content
Snippets Groups Projects
Commit d9041712 authored by gmantele's avatar gmantele
Browse files

[TAP & UWS] 2 MAJOR BUGS FIXED (these bugs were affecting performances).

1) [TAP & UWS] ]MAJOR BUG FIX: The abortion of an SQL query is now correctly
implemented. Before this fix, 2 mistakes prevented this clean abortion:
  a/ The thread was not cancelled because the SQL query execution was
blocking the thread. Then the thread could not treat the interruption though
it was flagged as interrupted.
  b/ The function UWSJob.isStopped() considered the job as stopped because
the interrupted flag was set, even though the thread was still processing
(and the database too). Because of that it returned true and the job phase
was ABORTED though the thread was still running.
  NOW:
  a/ TAPJob calls the function Statement.cancel() (if supported) in order
to cancel the SQL query execution properly inside the database.
  b/ The function UWSJob.isStopped() does not test any more the interrupted flag
and returns true only if the thread is really stopped.
  IN BRIEF: It is now sure that a job in the phase ABORTED is really stopped
(that's to say: thread stopped AND DB query execution stopped).

2) [TAP] BUG FIX: When the writing of a result is abnormaly interrupted for any
reason, the file which was being written is deleted.
parent 2b505506
Branches
No related tags found
No related merge requests found
...@@ -104,7 +104,7 @@ import adql.query.ADQLQuery; ...@@ -104,7 +104,7 @@ import adql.query.ADQLQuery;
* </p> * </p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (04/2015) * @version 2.1 (11/2015)
*/ */
public class ADQLExecutor { public class ADQLExecutor {
...@@ -224,9 +224,20 @@ public class ADQLExecutor { ...@@ -224,9 +224,20 @@ public class ADQLExecutor {
try{ try{
return start(); return start();
}catch(IOException ioe){ }catch(IOException ioe){
if (thread.isInterrupted())
return report;
else
throw new UWSException(ioe); throw new UWSException(ioe);
}catch(TAPException te){ }catch(TAPException te){
if (thread.isInterrupted())
return report;
else
throw new UWSException(te.getHttpErrorCode(), te); throw new UWSException(te.getHttpErrorCode(), te);
}catch(UWSException ue){
if (thread.isInterrupted())
return report;
else
throw ue;
} }
} }
...@@ -249,6 +260,17 @@ public class ADQLExecutor { ...@@ -249,6 +260,17 @@ public class ADQLExecutor {
dbConn = service.getFactory().getConnection(jobID); dbConn = service.getFactory().getConnection(jobID);
} }
/**
* Cancel the current SQL query execution or result set fetching if any is currently running.
* If no such process is on going, this function has no effect.
*
* @since 2.1
*/
public final void cancelQuery(){
if (dbConn != null && progression == ExecutionProgression.EXECUTING_ADQL)
dbConn.cancel(true);
}
/** /**
* <p>Start the synchronous processing of the ADQL query.</p> * <p>Start the synchronous processing of the ADQL query.</p>
* *
...@@ -625,12 +647,14 @@ public class ADQLExecutor { ...@@ -625,12 +647,14 @@ public class ADQLExecutor {
} }
// CASE ASYNCHRONOUS: // CASE ASYNCHRONOUS:
else{ else{
boolean completed = false;
long start = -1, end = -1; long start = -1, end = -1;
Result result = null;
JobThread jobThread = (JobThread)thread;
try{ try{
// Create a UWS Result object to store the result // Create a UWS Result object to store the result
// (the result will be stored in a file and this object is the association between the job and the result file): // (the result will be stored in a file and this object is the association between the job and the result file):
JobThread jobThread = (JobThread)thread; result = jobThread.createResult();
Result result = jobThread.createResult();
// Set the MIME type of the result format in the result description: // Set the MIME type of the result format in the result description:
result.setMimeType(formatter.getMimeType()); result.setMimeType(formatter.getMimeType());
...@@ -646,10 +670,25 @@ public class ADQLExecutor { ...@@ -646,10 +670,25 @@ public class ADQLExecutor {
// Add the result description and link in the job description: // Add the result description and link in the job description:
jobThread.publishResult(result); jobThread.publishResult(result);
completed = true;
logger.logTAP(LogLevel.INFO, report, "RESULT_WRITTEN", "Result formatted (in " + formatter.getMimeType() + " ; " + (report.nbRows < 0 ? "?" : report.nbRows) + " rows ; " + ((report.resultingColumns == null) ? "?" : report.resultingColumns.length) + " columns) in " + ((start <= 0 || end <= 0) ? "?" : (end - start)) + "ms!", null); logger.logTAP(LogLevel.INFO, report, "RESULT_WRITTEN", "Result formatted (in " + formatter.getMimeType() + " ; " + (report.nbRows < 0 ? "?" : report.nbRows) + " rows ; " + ((report.resultingColumns == null) ? "?" : report.resultingColumns.length) + " columns) in " + ((start <= 0 || end <= 0) ? "?" : (end - start)) + "ms!", null);
}catch(IOException ioe){ }catch(IOException ioe){
// Propagate the exception:
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Impossible to write in the file into the result of the job " + report.jobID + " must be written!"); throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, ioe, "Impossible to write in the file into the result of the job " + report.jobID + " must be written!");
}finally{
if (!completed){
// Delete the result file (it is either incomplete or incorrect ;
// it is then not reliable and is anyway not associated with the job and so could not be later deleted when the job will be):
if (result != null){
try{
service.getFileManager().deleteResult(result, jobThread.getJob());
}catch(IOException ioe){
logger.logTAP(LogLevel.ERROR, report, "WRITING_RESULT", "The result writting has failed and the produced partial result must be deleted, but this deletion also failed! (job: " + report.jobID + ")", ioe);
}
}
}
} }
} }
} }
......
...@@ -45,7 +45,7 @@ import uws.service.log.UWSLog.LogLevel; ...@@ -45,7 +45,7 @@ import uws.service.log.UWSLog.LogLevel;
* </p> * </p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (04/2015) * @version 2.1 (11/2015)
*/ */
public class TAPJob extends UWSJob { public class TAPJob extends UWSJob {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
...@@ -341,6 +341,31 @@ public class TAPJob extends UWSJob { ...@@ -341,6 +341,31 @@ public class TAPJob extends UWSJob {
} }
} }
/** @since 2.1 */
@Override
protected void stop(){
if (!isStopped()){
synchronized(thread){
stopping = true;
// Interrupts the thread:
thread.interrupt();
// Cancel the query execution if any currently running:
((AsyncThread)thread).executor.cancelQuery();
// Wait a little for its end:
if (waitForStop > 0){
try{
thread.join(waitForStop);
}catch(InterruptedException ie){
getLogger().logJob(LogLevel.WARNING, this, "END", "Unexpected InterruptedException while waiting for the end of the execution of the job \"" + jobId + "\" (thread ID: " + thread.getId() + ")!", ie);
}
}
}
}
}
/** /**
* This exception is thrown by a job execution when no database connection are available anymore. * This exception is thrown by a job execution when no database connection are available anymore.
* *
......
...@@ -74,7 +74,7 @@ import adql.translator.PostgreSQLTranslator; ...@@ -74,7 +74,7 @@ import adql.translator.PostgreSQLTranslator;
* </p> * </p>
* *
* @author Gr&eacute;gory Mantelet (ARI) * @author Gr&eacute;gory Mantelet (ARI)
* @version 2.0 (04/2015) * @version 2.1 (11/2015)
* @since 2.0 * @since 2.0
*/ */
public final class ConfigurableTAPFactory extends AbstractTAPFactory { public final class ConfigurableTAPFactory extends AbstractTAPFactory {
...@@ -281,6 +281,9 @@ public final class ConfigurableTAPFactory extends AbstractTAPFactory { ...@@ -281,6 +281,9 @@ public final class ConfigurableTAPFactory extends AbstractTAPFactory {
@Override @Override
public void freeConnection(DBConnection conn){ public void freeConnection(DBConnection conn){
try{ try{
// Cancel any possible query that could be running:
conn.cancel(false);
// Close the connection (if a connection pool is used, the connection is not really closed but is freed and kept in the pool for further usage):
((JDBCConnection)conn).getInnerConnection().close(); ((JDBCConnection)conn).getInnerConnection().close();
}catch(SQLException se){ }catch(SQLException se){
service.getLogger().error("Can not close properly the connection \"" + conn.getID() + "\"!", se); service.getLogger().error("Can not close properly the connection \"" + conn.getID() + "\"!", se);
......
...@@ -22,6 +22,7 @@ package tap.data; ...@@ -22,6 +22,7 @@ package tap.data;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
...@@ -42,11 +43,16 @@ import adql.translator.JDBCTranslator; ...@@ -42,11 +43,16 @@ import adql.translator.JDBCTranslator;
* </i></p> * </i></p>
* *
* @author Gr&eacute;gory Mantelet (ARI) * @author Gr&eacute;gory Mantelet (ARI)
* @version 2.1 (10/2015) * @version 2.1 (11/2015)
* @since 2.0 * @since 2.0
*/ */
public class ResultSetTableIterator implements TableIterator { public class ResultSetTableIterator implements TableIterator {
/** Statement associated with the ResultSet/Dataset to read.
* <i>MAY be NULL</i>
* @since 2.1 */
private final Statement stmt;
/** ResultSet/Dataset to read. */ /** ResultSet/Dataset to read. */
private final ResultSet data; private final ResultSet data;
...@@ -90,10 +96,41 @@ public class ResultSetTableIterator implements TableIterator { ...@@ -90,10 +96,41 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched. * @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
* *
* @see #convertType(int, String, String) * @see #convertType(int, String, String)
* @see #ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[]) * @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/ */
public ResultSetTableIterator(final ResultSet dataSet) throws NullPointerException, DataReadException{ public ResultSetTableIterator(final ResultSet dataSet) throws NullPointerException, DataReadException{
this(dataSet, null, null, null); this(null, dataSet, null, null, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which deals with the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by a translator. That's why it is recommended
* to use one of the constructor having a {@link JDBCTranslator} in parameter.
* </p>
*
* @param dataSet Dataset over which this iterator must iterate.
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet) throws NullPointerException, DataReadException{
this(stmt, dataSet, null, null, null);
} }
/** /**
...@@ -127,10 +164,49 @@ public class ResultSetTableIterator implements TableIterator { ...@@ -127,10 +164,49 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched. * @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
* *
* @see #convertType(int, String, String) * @see #convertType(int, String, String)
* @see ResultSetTableIterator#ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[]) * @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/ */
public ResultSetTableIterator(final ResultSet dataSet, final String dbms) throws NullPointerException, DataReadException{ public ResultSetTableIterator(final ResultSet dataSet, final String dbms) throws NullPointerException, DataReadException{
this(dataSet, null, dbms, null); this(null, dataSet, null, dbms, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which deals with the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by a translator. That's why it is recommended
* to use one of the constructor having a {@link JDBCTranslator} in parameter.
* </p>
*
* <p><i><b>Important</b>:
* The second parameter of this constructor is given as second parameter of {@link #convertType(int, String, String)}.
* <b>This parameter is really used ONLY when the DBMS is SQLite ("sqlite").</b>
* Indeed, SQLite has so many datatype restrictions that it is absolutely needed to know it is the DBMS from which the
* ResultSet is coming. Without this information, type guessing will be unpredictable!
* </i></p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param dbms Lower-case string which indicates from which DBMS the given ResultSet is coming. <i>note: MAY be NULL.</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final String dbms) throws NullPointerException, DataReadException{
this(stmt, dataSet, null, dbms, null);
} }
/** /**
...@@ -159,10 +235,44 @@ public class ResultSetTableIterator implements TableIterator { ...@@ -159,10 +235,44 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched. * @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
* *
* @see #convertType(int, String, String) * @see #convertType(int, String, String)
* @see ResultSetTableIterator#ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[]) * @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/ */
public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator) throws NullPointerException, DataReadException{ public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator) throws NullPointerException, DataReadException{
this(dataSet, translator, null, null); this(null, dataSet, translator, null, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which will ask to the given translator ({@link JDBCTranslator#convertTypeFromDB(int, String, String, String[])})
* if not NULL. However if no translator is provided, this function will proceed to a default conversion
* using the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by the translator.
* </p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param translator The {@link JDBCTranslator} used to transform the ADQL query into SQL query. This translator is also able to convert
* JDBC types and to parse geometrical values. <i>note: MAY be NULL</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final JDBCTranslator translator) throws NullPointerException, DataReadException{
this(stmt, dataSet, translator, null, null);
} }
/** /**
...@@ -199,10 +309,52 @@ public class ResultSetTableIterator implements TableIterator { ...@@ -199,10 +309,52 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched. * @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
* *
* @see #convertType(int, String, String) * @see #convertType(int, String, String)
* @see ResultSetTableIterator#ResultSetTableIterator(ResultSet, JDBCTranslator, String, DBColumn[]) * @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/ */
public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator, final String dbms) throws NullPointerException, DataReadException{ public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator, final String dbms) throws NullPointerException, DataReadException{
this(dataSet, translator, dbms, null); this(null, dataSet, translator, dbms, null);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is trying to guess the datatype
* from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which will ask to the given translator ({@link JDBCTranslator#convertTypeFromDB(int, String, String, String[])})
* if not NULL. However if no translator is provided, this function will proceed to a default conversion
* using the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by the translator.
* </p>
*
* <p><i><b>Important</b>:
* The third parameter of this constructor is given as second parameter of {@link #convertType(int, String, String)}.
* <b>This parameter is really used ONLY when the translator conversion failed and when the DBMS is SQLite ("sqlite").</b>
* Indeed, SQLite has so many datatype restrictions that it is absolutely needed to know it is the DBMS from which the
* ResultSet is coming. Without this information, type guessing will be unpredictable!
* </i></p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param translator The {@link JDBCTranslator} used to transform the ADQL query into SQL query. This translator is also able to convert
* JDBC types and to parse geometrical values. <i>note: MAY be NULL</i>
* @param dbms Lower-case string which indicates from which DBMS the given ResultSet is coming. <i>note: MAY be NULL.</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the given ResultSet is closed or if the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final JDBCTranslator translator, final String dbms) throws NullPointerException, DataReadException{
this(stmt, dataSet, translator, dbms, null);
} }
/** /**
...@@ -256,12 +408,74 @@ public class ResultSetTableIterator implements TableIterator { ...@@ -256,12 +408,74 @@ public class ResultSetTableIterator implements TableIterator {
* @throws DataReadException If the metadata (columns count and types) can not be fetched. * @throws DataReadException If the metadata (columns count and types) can not be fetched.
* *
* @see #convertType(int, String, String) * @see #convertType(int, String, String)
* @see #ResultSetTableIterator(Statement, ResultSet, JDBCTranslator, String, DBColumn[])
*/ */
public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator, final String dbms, final DBColumn[] resultMeta) throws NullPointerException, DataReadException{ public ResultSetTableIterator(final ResultSet dataSet, final JDBCTranslator translator, final String dbms, final DBColumn[] resultMeta) throws NullPointerException, DataReadException{
this(null, dataSet, translator, dbms, resultMeta);
}
/**
* <p>Build a TableIterator able to read rows and columns of the given ResultSet.</p>
*
* <p>
* In order to provide the metadata through {@link #getMetadata()}, this constructor is reading first the given metadata (if any),
* and then, try to guess the datatype from the DBMS column datatype (using {@link #convertType(int, String, String)}).
* </p>
*
* <h3>Provided metadata</h3>
*
* <p>The third parameter of this constructor aims to provide the metadata expected for each column of the ResultSet.</p>
*
* <p>
* For that, it is expected that all these metadata are {@link TAPColumn} objects. Indeed, simple {@link DBColumn}
* instances do not have the type information. If just {@link DBColumn}s are provided, the ADQL name it provides will be kept
* but the type will be guessed from the type provide by the ResultSetMetadata.
* </p>
*
* <p><i>Note:
* If this parameter is incomplete (array length less than the column count returned by the ResultSet or some array items are NULL),
* column metadata will be associated in the same order as the ResultSet columns. Missing metadata will be built from the
* {@link ResultSetMetaData} and so the types will be guessed.
* </i></p>
*
* <h3>Type guessing</h3>
*
* <p>
* In order to guess a TAP type from a DBMS type, this constructor will call {@link #convertType(int, String, String)}
* which will ask to the given translator ({@link JDBCTranslator#convertTypeFromDB(int, String, String, String[])})
* if not NULL. However if no translator is provided, this function will proceed to a default conversion
* using the most common standard datatypes known in Postgres, SQLite, MySQL, Oracle and JavaDB/Derby.
* This conversion is therefore not as precise as the one expected by the translator.
* </p>
*
* <p><i><b>Important</b>:
* The third parameter of this constructor is given as second parameter of {@link #convertType(int, String, String)}.
* <b>This parameter is really used ONLY when the translator conversion failed and when the DBMS is SQLite ("sqlite").</b>
* Indeed, SQLite has so many datatype restrictions that it is absolutely needed to know it is the DBMS from which the
* ResultSet is coming. Without this information, type guessing will be unpredictable!
* </i></p>
*
* @param dataSet Dataset over which this iterator must iterate.
* @param translator The {@link JDBCTranslator} used to transform the ADQL query into SQL query. This translator is also able to convert
* JDBC types and to parse geometrical values. <i>note: MAY be NULL</i>
* @param dbms Lower-case string which indicates from which DBMS the given ResultSet is coming. <i>note: MAY be NULL.</i>
* @param resultMeta List of expected columns. <i>note: these metadata are expected to be really {@link TAPColumn} objects ; MAY be NULL.</i>
*
* @throws NullPointerException If NULL is given in parameter.
* @throws DataReadException If the metadata (columns count and types) can not be fetched.
*
* @see #convertType(int, String, String)
*
* @since 2.1
*/
public ResultSetTableIterator(final Statement stmt, final ResultSet dataSet, final JDBCTranslator translator, final String dbms, final DBColumn[] resultMeta) throws NullPointerException, DataReadException{
// A dataset MUST BE provided: // A dataset MUST BE provided:
if (dataSet == null) if (dataSet == null)
throw new NullPointerException("Missing ResultSet object over which to iterate!"); throw new NullPointerException("Missing ResultSet object over which to iterate!");
// Set the associated statement:
this.stmt = stmt;
// Keep a reference to the ResultSet: // Keep a reference to the ResultSet:
data = dataSet; data = dataSet;
...@@ -296,10 +510,17 @@ public class ResultSetTableIterator implements TableIterator { ...@@ -296,10 +510,17 @@ public class ResultSetTableIterator implements TableIterator {
@Override @Override
public void close() throws DataReadException{ public void close() throws DataReadException{
boolean rsClosed = false;
try{ try{
data.close(); data.close();
rsClosed = true;
if (stmt != null)
stmt.close();
}catch(SQLException se){ }catch(SQLException se){
if (!rsClosed)
throw new DataReadException("Can not close the iterated ResultSet!", se); throw new DataReadException("Can not close the iterated ResultSet!", se);
else
throw new DataReadException("ResultSet successfully closed but impossible to closed the associated Statement!", se);
} }
} }
......
...@@ -42,7 +42,7 @@ import adql.query.ADQLQuery; ...@@ -42,7 +42,7 @@ import adql.query.ADQLQuery;
* </p> * </p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (03/2015) * @version 2.1 (11/2015)
*/ */
public interface DBConnection { public interface DBConnection {
...@@ -268,4 +268,31 @@ public interface DBConnection { ...@@ -268,4 +268,31 @@ public interface DBConnection {
*/ */
public void setFetchSize(final int size); public void setFetchSize(final int size);
/**
* <p>Stop the execution of the current query.</p>
*
* <p>
* If asked. a rollback of the current transaction can also be performed
* after the cancellation (if successful) of the query.
* </p>
*
* <p>
* This function should <b>never</b> return any kind of exception. This is particularly important
* in the following cases:
* </p>
* <ul>
* <li>this function is not implemented</li>
* <li>the database driver or another API used to interact with a "database" does not support the cancel operation</li>
* <li>no query is currently running</li>
* <li>a rollback is not possible or failed</li>
* </ul>
* <p>However, if an exception occurs it may be directly logged at least as a WARNING.</p>
*
* @param rollback <code>true</code> to cancel the statement AND rollback the current connection transaction,
* <code>false</code> to just cancel the statement.
*
* @since 2.1
*/
public void cancel(final boolean rollback);
} }
...@@ -27,6 +27,7 @@ import java.sql.DriverManager; ...@@ -27,6 +27,7 @@ import java.sql.DriverManager;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement; import java.sql.Statement;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.text.ParseException; import java.text.ParseException;
...@@ -70,6 +71,29 @@ import adql.translator.TranslationException; ...@@ -70,6 +71,29 @@ import adql.translator.TranslationException;
* Then it has been really tested successfully with Postgres and SQLite. * Then it has been really tested successfully with Postgres and SQLite.
* </i></p> * </i></p>
* *
*
* <h3>Only one query executed at a time!</h3>
*
* <p>
* With a single instance of {@link JDBCConnection} it is possible to execute only one query (whatever the type: SELECT, UPDATE, DELETE, ...)
* at a time. This is indeed the simple way chosen with this implementation in order to allow the cancellation of any query by managing only
* one {@link Statement}. Indeed, only a {@link Statement} has a cancel function able to stop any query execution on the database.
* So all queries are executed with the same {@link Statement}. Thus, allowing the execution of one query at a time lets
* abort only one query rather than several in once (though just one should have been stopped).
* </p>
*
* <p>
* All the following functions are synchronized in order to prevent parallel execution of them by several threads:
* {@link #addUploadedTable(TAPTable, TableIterator)}, {@link #dropUploadedTable(TAPTable)}, {@link #executeQuery(ADQLQuery)},
* {@link #getTAPSchema()} and {@link #setTAPSchema(TAPMetadata)}.
* </p>
*
* <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.
* </p>
*
*
* <h3>Deal with different DBMS features</h3> * <h3>Deal with different DBMS features</h3>
* *
* <p>Update queries are taking into account whether the following features are supported by the DBMS:</p> * <p>Update queries are taking into account whether the following features are supported by the DBMS:</p>
...@@ -99,6 +123,7 @@ import adql.translator.TranslationException; ...@@ -99,6 +123,7 @@ import adql.translator.TranslationException;
* All these features have no impact at all on ADQL query executions ({@link #executeQuery(ADQLQuery)}). * All these features have no impact at all on ADQL query executions ({@link #executeQuery(ADQLQuery)}).
* </i></p> * </i></p>
* *
*
* <h3>Datatypes</h3> * <h3>Datatypes</h3>
* *
* <p> * <p>
...@@ -119,6 +144,7 @@ import adql.translator.TranslationException; ...@@ -119,6 +144,7 @@ import adql.translator.TranslationException;
* and managed. * and managed.
* </p> * </p>
* *
*
* <h3>Fetch size</h3> * <h3>Fetch size</h3>
* *
* <p> * <p>
...@@ -144,7 +170,7 @@ import adql.translator.TranslationException; ...@@ -144,7 +170,7 @@ import adql.translator.TranslationException;
* </i></p> * </i></p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.1 (07/2015) * @version 2.1 (11/2015)
* @since 2.0 * @since 2.0
*/ */
public class JDBCConnection implements DBConnection { public class JDBCConnection implements DBConnection {
...@@ -170,6 +196,31 @@ public class JDBCConnection implements DBConnection { ...@@ -170,6 +196,31 @@ public class JDBCConnection implements DBConnection {
/** JDBC connection (created and initialized at the creation of this {@link JDBCConnection} instance). */ /** JDBC connection (created and initialized at the creation of this {@link JDBCConnection} instance). */
protected final Connection connection; protected final Connection connection;
/** <p>The only {@link Statement} instance that should be used in this {@link JDBCConnection}.
* Having the same {@link Statement} for all the interactions with the database lets cancel any when needed (e.g. when the execution is too long).</p>
* <p>This statement is by default NULL ; it must be initialized by the function {@link #getStatement()}.</p>
* @since 2.1 */
protected Statement stmt = null;
/**
* <p>It <code>true</code>, this flag indicates that the function {@link #cancel(boolean)} has been called successfully.</p>
*
* <p>{@link #cancel(boolean)} sets this flag to <code>true</code>.</p>
* <p>
* All functions executing any kind of query on the database MUST set this flag to <code>false</code> before doing anything
* 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>
* @since 2.1 */
private Boolean cancelled = false;
/** The translator this connection must use to translate ADQL into SQL. It is also used to get information about the case sensitivity of all types of identifier (schema, table, column). */ /** The translator this connection must use to translate ADQL into SQL. It is also used to get information about the case sensitivity of all types of identifier (schema, table, column). */
protected final JDBCTranslator translator; protected final JDBCTranslator translator;
...@@ -198,6 +249,13 @@ public class JDBCConnection implements DBConnection { ...@@ -198,6 +249,13 @@ public class JDBCConnection implements DBConnection {
/** Indicate whether the DBMS has the notion of SCHEMA. Most of the DBMS has it, but not SQLite for instance. <i>note: If not supported, the DB table name will be prefixed by the DB schema name followed by the character "_". Nevertheless, if the DB schema name is NULL, the DB table name will never be prefixed.</i> */ /** Indicate whether the DBMS has the notion of SCHEMA. Most of the DBMS has it, but not SQLite for instance. <i>note: If not supported, the DB table name will be prefixed by the DB schema name followed by the character "_". Nevertheless, if the DB schema name is NULL, the DB table name will never be prefixed.</i> */
protected boolean supportsSchema; protected boolean supportsSchema;
/** <p>Indicate whether a DBMS statement is able to cancel a query execution.</p>
* <p> Since this information is not provided by {@link DatabaseMetaData} a first attempt is always performed.
* In case a {@link SQLFeatureNotSupportedException} is caught, this flag is set to false preventing any further
* attempt of canceling a query.</p>
* @since 2.1 */
protected boolean supportsCancel = true;
/* CASE SENSITIVITY SUPPORT */ /* CASE SENSITIVITY SUPPORT */
/** Indicate whether UNquoted identifiers will be considered as case INsensitive and stored in mixed case by the DBMS. <i>note: If FALSE, unquoted identifiers will still be considered as case insensitive for the researches, but will be stored in lower or upper case (in function of {@link #lowerCaseUnquoted} and {@link #upperCaseUnquoted}). If none of these two flags is TRUE, the storage case will be though considered as mixed.</i> */ /** Indicate whether UNquoted identifiers will be considered as case INsensitive and stored in mixed case by the DBMS. <i>note: If FALSE, unquoted identifiers will still be considered as case insensitive for the researches, but will be stored in lower or upper case (in function of {@link #lowerCaseUnquoted} and {@link #upperCaseUnquoted}). If none of these two flags is TRUE, the storage case will be though considered as mixed.</i> */
...@@ -397,11 +455,196 @@ public class JDBCConnection implements DBConnection { ...@@ -397,11 +455,196 @@ public class JDBCConnection implements DBConnection {
return connection; return connection;
} }
/**
* <p>Get the only statement associated with this {@link JDBCConnection}.</p>
*
* <p>
* If no {@link Statement} is yet existing, one is created, stored in this {@link JDBCConnection} (for further uses)
* and then returned.
* </p>
*
* @return The {@link Statement} instance associated with this {@link JDBCConnection}. <i>Never NULL</i>
*
* @throws SQLException In case a {@link Statement} can not be created.
*
* @since 2.1
*/
protected Statement getStatement() throws SQLException{
if (stmt == null || stmt.isClosed())
return (stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
else
return stmt;
}
/**
* Close the only statement associated with this {@link JDBCConnection}.
*
* @since 2.1
*/
protected void closeStatement(){
close(stmt);
stmt = null;
}
/**
* <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.
* </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.
* </p>
*
* <p><i>Note 1:
* A failure of a rollback is not considered as a not supported cancellation feature by the JDBC driver or the DBMS.
* So if the cancellation succeeds but a rollback fails, a next call of this function will still try cancelling the given statement.
* </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>
*
* @param rollback The statement to cancel. <i>Note: if closed or NULL, nothing will be done and no exception will be thrown.</i>
*
* @see DBConnection#cancel(boolean)
* @see #cancel(Statement, boolean)
*
* @since 2.1
*/
@Override
public final void cancel(final boolean rollback){
if (supportsCancel && stmt != null){
synchronized(cancelled){
cancelled = cancel(stmt, rollback);
// Log the success of the cancellation:
if (cancelled && logger != null)
logger.logDB(LogLevel.INFO, this, "CANCEL", "Query execution successfully stopped!", null);
}
}
}
/**
* <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.
* </p>
*
* <p><i>Note:
* A failure of a rollback is not considered as a not supported cancellation feature by the JDBC driver or the DBMS.
* So if the cancellation succeeds but a rollback fails, a next call of this function will still try canceling the given statement.
* </i></p>
*
* @param stmt The statement to cancel. <i>Note: if closed or NULL, nothing will be done and no exception will be thrown.</i>
* @param rollback <code>true</code> to cancel the statement AND rollback the current connection transaction,
* <code>false</code> to just cancel the statement.
*
* @return <code>true</code> if the cancellation succeeded (or none was running),
* <code>false</code> otherwise (and especially if the "cancel" operation is not supported).
*
* @since 2.1
*/
protected boolean cancel(final Statement stmt, final boolean rollback){
// Not supported "cancel" operation => fail!
if (!supportsCancel)
return false;
// No statement => "cancellation" successful!
if (stmt == null)
return true;
// If the statement is not already closed, cancel its current query execution:
try{
if (!stmt.isClosed()){
// Cancel the query execution:
stmt.cancel();
// Rollback all executed operations (only if in a transaction ; that's to say if AutoCommit = false):
if (rollback && supportsTransaction){
try{
if (!connection.getAutoCommit())
connection.rollback();
}catch(SQLException se){
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "CANCEL", "Query execution successfully stopped BUT the rollback fails!", se);
}
}
}
return true;
}catch(SQLFeatureNotSupportedException sfnse){
// prevent further cancel attempts:
supportsCancel = false;
// log a warning:
if (logger != null)
logger.logDB(LogLevel.WARNING, this, "CANCEL", "This JDBC driver does not support Statement.cancel(). No further cancel attempt will be performed with this JDBCConnection instance.", sfnse);
return false;
}catch(SQLException se){
if (logger != null)
logger.logDB(LogLevel.ERROR, this, "CANCEL", "Abortion of the current query apparently fails! The query may still run on the database server.", se);
return false;
}
}
/**
* <p>Tell whether the last query execution has been canceled.</p>
*
* <p><i>Note:
* 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>
*
* @return <code>true</code> if the last query execution has been cancelled,
* <code>false</code> otherwise.
*
* @since 2.1
*/
protected final boolean isCancelled(){
synchronized(cancelled){
return cancelled;
}
}
/**
* <p>Reset the {@link #cancelled} flag to <code>false</code>.</p>
*
* <p><i>Note:
* 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>
*
* @since 2.1
*/
protected final void resetCancel(){
synchronized(cancelled){
cancelled = false;
}
}
/* ********************* */ /* ********************* */
/* INTERROGATION METHODS */ /* INTERROGATION METHODS */
/* ********************* */ /* ********************* */
@Override @Override
public TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException{ public synchronized TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException{
// Starting of new query execution => disable the cancel flag:
resetCancel();
String sql = null; String sql = null;
ResultSet result = null; ResultSet result = null;
try{ try{
...@@ -415,21 +658,27 @@ public class JDBCConnection implements DBConnection { ...@@ -415,21 +658,27 @@ public class JDBCConnection implements DBConnection {
try{ try{
connection.setAutoCommit(false); connection.setAutoCommit(false);
}catch(SQLException se){ }catch(SQLException se){
if (!isCancelled()){
supportsFetchSize = false; supportsFetchSize = false;
if (logger != null) if (logger != null)
logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null); logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null);
} }
} }
Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); }
getStatement();
if (supportsFetchSize){ if (supportsFetchSize){
try{ try{
stmt.setFetchSize(fetchSize); stmt.setFetchSize(fetchSize);
}catch(SQLException se){ }catch(SQLException se){
if (!isCancelled()){
supportsFetchSize = false; supportsFetchSize = false;
if (logger != null) if (logger != null)
logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null); logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null);
} }
} }
}
// 3. Execute the SQL query: // 3. Execute the SQL query:
if (logger != null) if (logger != null)
...@@ -443,16 +692,19 @@ public class JDBCConnection implements DBConnection { ...@@ -443,16 +692,19 @@ public class JDBCConnection implements DBConnection {
}catch(SQLException se){ }catch(SQLException se){
close(result); close(result);
if (logger != null) closeStatement();
if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "EXECUTE", "Unexpected error while EXECUTING SQL query!", null); logger.logDB(LogLevel.ERROR, this, "EXECUTE", "Unexpected error while EXECUTING SQL query!", null);
throw new DBException("Unexpected error while executing a SQL query: " + se.getMessage(), se); throw new DBException("Unexpected error while executing a SQL query: " + se.getMessage(), se);
}catch(TranslationException te){ }catch(TranslationException te){
close(result); close(result);
closeStatement();
if (logger != null) if (logger != null)
logger.logDB(LogLevel.ERROR, this, "TRANSLATE", "Unexpected error while TRANSLATING ADQL into SQL!", null); logger.logDB(LogLevel.ERROR, this, "TRANSLATE", "Unexpected error while TRANSLATING ADQL into SQL!", null);
throw new DBException("Unexpected error while translating ADQL into SQL: " + te.getMessage(), te); throw new DBException("Unexpected error while translating ADQL into SQL: " + te.getMessage(), te);
}catch(DataReadException dre){ }catch(DataReadException dre){
close(result); close(result);
closeStatement();
if (logger != null) if (logger != null)
logger.logDB(LogLevel.ERROR, this, "RESULT", "Unexpected error while reading the query result!", null); logger.logDB(LogLevel.ERROR, this, "RESULT", "Unexpected error while reading the query result!", null);
throw new DBException("Impossible to read the query result, because: " + dre.getMessage(), dre); throw new DBException("Impossible to read the query result, because: " + dre.getMessage(), dre);
...@@ -460,7 +712,19 @@ public class JDBCConnection implements DBConnection { ...@@ -460,7 +712,19 @@ public class JDBCConnection implements DBConnection {
} }
/** /**
* Create a {@link TableIterator} instance which lets reading the given result table. * <p>Create a {@link TableIterator} instance which lets reading the given result table.</p>
*
* <p><b>Important note 1:</b>
* This function also set to NULL the statement of this {@link JDBCConnection} instance: {@link #stmt}.
* However, the statement is not closed ; it is just given to a {@link ResultSetTableIterator} iterator
* which will close it in the same time as the given {@link ResultSet}, when its function
* {@link ResultSetTableIterator#close()} is called.
* </p>
*
* <p><b>Important note 2:</b>
* In case an exception occurs within this function, the {@link ResultSet} and the {@link Statement}
* are <b>immediately closed</b> before propagating the exception.
* </p>
* *
* @param rs Result of an SQL query. * @param rs Result of an SQL query.
* @param resultingColumns Metadata corresponding to each columns of the result. * @param resultingColumns Metadata corresponding to each columns of the result.
...@@ -471,7 +735,19 @@ public class JDBCConnection implements DBConnection { ...@@ -471,7 +735,19 @@ public class JDBCConnection implements DBConnection {
* or if any other error occurs. * or if any other error occurs.
*/ */
protected TableIterator createTableIterator(final ResultSet rs, final DBColumn[] resultingColumns) throws DataReadException{ protected TableIterator createTableIterator(final ResultSet rs, final DBColumn[] resultingColumns) throws DataReadException{
return new ResultSetTableIterator(rs, translator, dbms, resultingColumns); // Dis-associate the current Statement from this JDBCConnection instance:
Statement itStmt = stmt;
stmt = null;
// Return a TableIterator wrapping the given ResultSet:
try{
return new ResultSetTableIterator(itStmt, rs, translator, dbms, resultingColumns);
}catch(Throwable t){
// In case of any kind of exception, the ResultSet and the Statement MUST be closed in order to save resources:
close(rs);
close(itStmt);
// Then, the caught exception can be thrown:
throw (t instanceof DataReadException) ? (DataReadException)t : new DataReadException(t);
}
} }
/* *********************** */ /* *********************** */
...@@ -528,7 +804,10 @@ public class JDBCConnection implements DBConnection { ...@@ -528,7 +804,10 @@ public class JDBCConnection implements DBConnection {
* @see tap.db.DBConnection#getTAPSchema() * @see tap.db.DBConnection#getTAPSchema()
*/ */
@Override @Override
public TAPMetadata getTAPSchema() throws DBException{ public synchronized TAPMetadata getTAPSchema() throws DBException{
// Starting of new query execution => disable the cancel flag:
resetCancel();
// Build a virgin TAP metadata: // Build a virgin TAP metadata:
TAPMetadata metadata = new TAPMetadata(); TAPMetadata metadata = new TAPMetadata();
...@@ -536,10 +815,9 @@ public class JDBCConnection implements DBConnection { ...@@ -536,10 +815,9 @@ public class JDBCConnection implements DBConnection {
TAPSchema tap_schema = TAPMetadata.getStdSchema(supportsSchema); TAPSchema tap_schema = TAPMetadata.getStdSchema(supportsSchema);
// LOAD ALL METADATA FROM THE STANDARD TAP TABLES: // LOAD ALL METADATA FROM THE STANDARD TAP TABLES:
Statement stmt = null;
try{ try{
// create a common statement for all loading functions: // create a common statement for all loading functions:
stmt = connection.createStatement(); getStatement();
// load all schemas from TAP_SCHEMA.schemas: // load all schemas from TAP_SCHEMA.schemas:
if (logger != null) if (logger != null)
...@@ -562,11 +840,11 @@ public class JDBCConnection implements DBConnection { ...@@ -562,11 +840,11 @@ public class JDBCConnection implements DBConnection {
loadKeys(tap_schema.getTable(STDTable.KEYS.label), tap_schema.getTable(STDTable.KEY_COLUMNS.label), lstTables, stmt); loadKeys(tap_schema.getTable(STDTable.KEYS.label), tap_schema.getTable(STDTable.KEY_COLUMNS.label), lstTables, stmt);
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create a Statement!", se); logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create a Statement!", se);
throw new DBException("Can not create a Statement!", se); throw new DBException("Can not create a Statement!", se);
}finally{ }finally{
close(stmt); closeStatement();
} }
return metadata; return metadata;
...@@ -617,7 +895,7 @@ public class JDBCConnection implements DBConnection { ...@@ -617,7 +895,7 @@ public class JDBCConnection implements DBConnection {
metadata.addSchema(newSchema); metadata.addSchema(newSchema);
} }
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load schemas from TAP_SCHEMA.schemas!", se); logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load schemas from TAP_SCHEMA.schemas!", se);
throw new DBException("Impossible to load schemas from TAP_SCHEMA.schemas!", se); throw new DBException("Impossible to load schemas from TAP_SCHEMA.schemas!", se);
}finally{ }finally{
...@@ -711,7 +989,7 @@ public class JDBCConnection implements DBConnection { ...@@ -711,7 +989,7 @@ public class JDBCConnection implements DBConnection {
return lstTables; return lstTables;
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load tables from TAP_SCHEMA.tables!", se); logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load tables from TAP_SCHEMA.tables!", se);
throw new DBException("Impossible to load tables from TAP_SCHEMA.tables!", se); throw new DBException("Impossible to load tables from TAP_SCHEMA.tables!", se);
}finally{ }finally{
...@@ -799,7 +1077,7 @@ public class JDBCConnection implements DBConnection { ...@@ -799,7 +1077,7 @@ public class JDBCConnection implements DBConnection {
table.addColumn(newColumn); table.addColumn(newColumn);
} }
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se); logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se);
throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se); throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se);
}finally{ }finally{
...@@ -873,7 +1151,7 @@ public class JDBCConnection implements DBConnection { ...@@ -873,7 +1151,7 @@ public class JDBCConnection implements DBConnection {
while(rsKeyCols.next()) while(rsKeyCols.next())
columns.put(rsKeyCols.getString(1), rsKeyCols.getString(2)); columns.put(rsKeyCols.getString(1), rsKeyCols.getString(2));
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se); logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se);
throw new DBException("Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se); throw new DBException("Impossible to load key columns from TAP_SCHEMA.key_columns for the foreign key: \"" + key_id + "\"!", se);
}finally{ }finally{
...@@ -884,13 +1162,13 @@ public class JDBCConnection implements DBConnection { ...@@ -884,13 +1162,13 @@ public class JDBCConnection implements DBConnection {
try{ try{
sourceTable.addForeignKey(key_id, targetTable, columns, nullifyIfNeeded(description), nullifyIfNeeded(utype)); sourceTable.addForeignKey(key_id, targetTable, columns, nullifyIfNeeded(description), nullifyIfNeeded(utype));
}catch(Exception ex){ }catch(Exception ex){
if (logger != null) if ((ex instanceof SQLException && !isCancelled()) && logger != null)
logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex); logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex);
throw new DBException("Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex); throw new DBException("Impossible to create the foreign key \"" + key_id + "\" because: " + ex.getMessage(), ex);
} }
} }
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se); logger.logDB(LogLevel.ERROR, this, "LOAD_TAP_SCHEMA", "Impossible to load columns from TAP_SCHEMA.columns!", se);
throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se); throw new DBException("Impossible to load columns from TAP_SCHEMA.columns!", se);
}finally{ }finally{
...@@ -924,8 +1202,9 @@ public class JDBCConnection implements DBConnection { ...@@ -924,8 +1202,9 @@ public class JDBCConnection implements DBConnection {
* @see tap.db.DBConnection#setTAPSchema(tap.metadata.TAPMetadata) * @see tap.db.DBConnection#setTAPSchema(tap.metadata.TAPMetadata)
*/ */
@Override @Override
public void setTAPSchema(final TAPMetadata metadata) throws DBException{ public synchronized void setTAPSchema(final TAPMetadata metadata) throws DBException{
Statement stmt = null; // Starting of new query execution => disable the cancel flag:
resetCancel();
try{ try{
// A. GET THE DEFINITION OF ALL STANDARD TAP TABLES: // A. GET THE DEFINITION OF ALL STANDARD TAP TABLES:
...@@ -934,7 +1213,7 @@ public class JDBCConnection implements DBConnection { ...@@ -934,7 +1213,7 @@ public class JDBCConnection implements DBConnection {
startTransaction(); startTransaction();
// B. RE-CREATE THE STANDARD TAP_SCHEMA TABLES: // B. RE-CREATE THE STANDARD TAP_SCHEMA TABLES:
stmt = connection.createStatement(); getStatement();
// 1. Ensure TAP_SCHEMA exists and drop all its standard TAP tables: // 1. Ensure TAP_SCHEMA exists and drop all its standard TAP tables:
if (logger != null) if (logger != null)
...@@ -960,12 +1239,12 @@ public class JDBCConnection implements DBConnection { ...@@ -960,12 +1239,12 @@ public class JDBCConnection implements DBConnection {
commit(); commit();
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "CREATE_TAP_SCHEMA", "Impossible to SET TAP_SCHEMA in DB!", se); logger.logDB(LogLevel.ERROR, this, "CREATE_TAP_SCHEMA", "Impossible to SET TAP_SCHEMA in DB!", se);
rollback(); rollback();
throw new DBException("Impossible to SET TAP_SCHEMA in DB!", se); throw new DBException("Impossible to SET TAP_SCHEMA in DB!", se);
}finally{ }finally{
close(stmt); closeStatement();
endTransaction(); endTransaction();
} }
} }
...@@ -1639,21 +1918,23 @@ public class JDBCConnection implements DBConnection { ...@@ -1639,21 +1918,23 @@ public class JDBCConnection implements DBConnection {
* @see #checkUploadedTableDef(TAPTable) * @see #checkUploadedTableDef(TAPTable)
*/ */
@Override @Override
public boolean addUploadedTable(TAPTable tableDef, TableIterator data) throws DBException, DataReadException{ public synchronized boolean addUploadedTable(TAPTable tableDef, TableIterator data) throws DBException, DataReadException{
// If no table to upload, consider it has been dropped and return TRUE: // If no table to upload, consider it has been dropped and return TRUE:
if (tableDef == null) if (tableDef == null)
return true; return true;
// Starting of new query execution => disable the cancel flag:
resetCancel();
// Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD): // Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD):
checkUploadedTableDef(tableDef); checkUploadedTableDef(tableDef);
Statement stmt = null;
try{ try{
// Start a transaction: // Start a transaction:
startTransaction(); startTransaction();
// ...create a statement: // ...create a statement:
stmt = connection.createStatement(); getStatement();
DatabaseMetaData dbMeta = connection.getMetaData(); DatabaseMetaData dbMeta = connection.getMetaData();
...@@ -1704,7 +1985,7 @@ public class JDBCConnection implements DBConnection { ...@@ -1704,7 +1985,7 @@ public class JDBCConnection implements DBConnection {
}catch(SQLException se){ }catch(SQLException se){
rollback(); rollback();
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.WARNING, this, "ADD_UPLOAD_TABLE", "Impossible to create the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se); logger.logDB(LogLevel.WARNING, this, "ADD_UPLOAD_TABLE", "Impossible to create the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
throw new DBException("Impossible to create the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se); throw new DBException("Impossible to create the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
}catch(DBException de){ }catch(DBException de){
...@@ -1714,7 +1995,7 @@ public class JDBCConnection implements DBConnection { ...@@ -1714,7 +1995,7 @@ public class JDBCConnection implements DBConnection {
rollback(); rollback();
throw dre; throw dre;
}finally{ }finally{
close(stmt); closeStatement();
endTransaction(); endTransaction();
} }
} }
...@@ -1843,15 +2124,17 @@ public class JDBCConnection implements DBConnection { ...@@ -1843,15 +2124,17 @@ public class JDBCConnection implements DBConnection {
* @see #checkUploadedTableDef(TAPTable) * @see #checkUploadedTableDef(TAPTable)
*/ */
@Override @Override
public boolean dropUploadedTable(final TAPTable tableDef) throws DBException{ public synchronized boolean dropUploadedTable(final TAPTable tableDef) throws DBException{
// If no table to upload, consider it has been dropped and return TRUE: // If no table to upload, consider it has been dropped and return TRUE:
if (tableDef == null) if (tableDef == null)
return true; return true;
// Starting of new query execution => disable the cancel flag:
resetCancel();
// Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD): // Check the table is well defined (and particularly the schema is well set with an ADQL name = TAP_UPLOAD):
checkUploadedTableDef(tableDef); checkUploadedTableDef(tableDef);
Statement stmt = null;
try{ try{
// Check the existence of the table to drop: // Check the existence of the table to drop:
...@@ -1859,8 +2142,7 @@ public class JDBCConnection implements DBConnection { ...@@ -1859,8 +2142,7 @@ public class JDBCConnection implements DBConnection {
return true; return true;
// Execute the update: // Execute the update:
stmt = connection.createStatement(); int cnt = getStatement().executeUpdate("DROP TABLE " + translator.getTableName(tableDef, supportsSchema) + ";");
int cnt = stmt.executeUpdate("DROP TABLE " + translator.getTableName(tableDef, supportsSchema) + ";");
// Log the end: // Log the end:
if (logger != null){ if (logger != null){
...@@ -1874,11 +2156,11 @@ public class JDBCConnection implements DBConnection { ...@@ -1874,11 +2156,11 @@ public class JDBCConnection implements DBConnection {
return (cnt >= 0); return (cnt >= 0);
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.WARNING, this, "DROP_UPLOAD_TABLE", "Impossible to drop the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se); logger.logDB(LogLevel.WARNING, this, "DROP_UPLOAD_TABLE", "Impossible to drop the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
throw new DBException("Impossible to drop the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se); throw new DBException("Impossible to drop the uploaded table: " + translator.getTableName(tableDef, supportsSchema) + "!", se);
}finally{ }finally{
close(stmt); closeStatement();
} }
} }
...@@ -2208,17 +2490,32 @@ public class JDBCConnection implements DBConnection { ...@@ -2208,17 +2490,32 @@ public class JDBCConnection implements DBConnection {
* <p>If the given {@link Statement} is NULL, nothing (even exception/error) happens.</p> * <p>If the given {@link Statement} is NULL, nothing (even exception/error) happens.</p>
* *
* <p> * <p>
* The given statement is explicitly canceled by this function before being closed.
* Thus the corresponding DBMS process is ensured to be stopped. Of course, this
* cancellation is effective only if this operation is supported by the JDBC driver
* and the DBMS.
* </p>
*
* <p><b>Important note:</b>
* In case of cancellation, <b>NO</b> rollback is performed.
* </p>
*
* <p>
* If any {@link SQLException} occurs during this operation, it is caught and just logged * If any {@link SQLException} occurs during this operation, it is caught and just logged
* (see {@link TAPLog#logDB(uws.service.log.UWSLog.LogLevel, DBConnection, String, String, Throwable)}). * (see {@link TAPLog#logDB(uws.service.log.UWSLog.LogLevel, DBConnection, String, String, Throwable)}).
* No error is thrown and nothing else is done. * No error is thrown and nothing else is done.
* </p> * </p>
* *
* @param stmt {@link Statement} to close. * @param stmt {@link Statement} to close.
*
* @see #cancel(Statement, boolean)
*/ */
protected final void close(final Statement stmt){ protected final void close(final Statement stmt){
try{ try{
if (stmt != null) if (stmt != null){
cancel(stmt, false);
stmt.close(); stmt.close();
}
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (logger != null)
logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a Statement!", null); logger.logDB(LogLevel.WARNING, this, "CLOSE", "Can not close a Statement!", null);
...@@ -2624,6 +2921,7 @@ public class JDBCConnection implements DBConnection { ...@@ -2624,6 +2921,7 @@ public class JDBCConnection implements DBConnection {
try{ try{
stmt.addBatch(); stmt.addBatch();
}catch(SQLException se){ }catch(SQLException se){
if (!isCancelled())
supportsBatchUpdates = false; supportsBatchUpdates = false;
/* /*
* If the error happens for the first row, it is still possible to insert all rows * If the error happens for the first row, it is still possible to insert all rows
...@@ -2633,10 +2931,10 @@ public class JDBCConnection implements DBConnection { ...@@ -2633,10 +2931,10 @@ public class JDBCConnection implements DBConnection {
* and must stop the whole TAP_SCHEMA initialization. * and must stop the whole TAP_SCHEMA initialization.
*/ */
if (indRow == 1){ if (indRow == 1){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "BATCH query impossible => TRYING AGAIN IN A NORMAL EXECUTION (executeUpdate())!", se); logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "BATCH query impossible => TRYING AGAIN IN A NORMAL EXECUTION (executeUpdate())!", se);
}else{ }else{
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH query impossible!", se); logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH query impossible!", se);
throw new DBException("BATCH query impossible!", se); throw new DBException("BATCH query impossible!", se);
} }
...@@ -2689,9 +2987,11 @@ public class JDBCConnection implements DBConnection { ...@@ -2689,9 +2987,11 @@ public class JDBCConnection implements DBConnection {
try{ try{
rows = stmt.executeBatch(); rows = stmt.executeBatch();
}catch(SQLException se){ }catch(SQLException se){
if (!isCancelled()){
supportsBatchUpdates = false; supportsBatchUpdates = false;
if (logger != null) if (logger != null)
logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH execution impossible!", se); logger.logDB(LogLevel.ERROR, this, "EXEC_UPDATE", "BATCH execution impossible!", se);
}
throw new DBException("BATCH execution impossible!", se); throw new DBException("BATCH execution impossible!", se);
} }
...@@ -2699,7 +2999,7 @@ public class JDBCConnection implements DBConnection { ...@@ -2699,7 +2999,7 @@ public class JDBCConnection implements DBConnection {
try{ try{
stmt.clearBatch(); stmt.clearBatch();
}catch(SQLException se){ }catch(SQLException se){
if (logger != null) if (!isCancelled() && logger != null)
logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "CLEAR BATCH impossible!", se); logger.logDB(LogLevel.WARNING, this, "EXEC_UPDATE", "CLEAR BATCH impossible!", se);
} }
......
...@@ -36,7 +36,7 @@ import uk.ac.starlink.table.StoragePolicy; ...@@ -36,7 +36,7 @@ import uk.ac.starlink.table.StoragePolicy;
* Format any given query (table) result into FITS. * Format any given query (table) result into FITS.
* *
* @author Gr&eacute;gory Mantelet (ARI) * @author Gr&eacute;gory Mantelet (ARI)
* @version 2.0 (04/2015) * @version 2.1 (11/2015)
* @since 2.0 * @since 2.0
*/ */
public class FITSFormat implements OutputFormat { public class FITSFormat implements OutputFormat {
...@@ -84,15 +84,21 @@ public class FITSFormat implements OutputFormat { ...@@ -84,15 +84,21 @@ public class FITSFormat implements OutputFormat {
ColumnInfo[] colInfos = VOTableFormat.toColumnInfos(result, execReport, thread); ColumnInfo[] colInfos = VOTableFormat.toColumnInfos(result, execReport, thread);
// Turns the result set into a table: // Turns the result set into a table:
LimitedStarTable table = new LimitedStarTable(result, colInfos, execReport.parameters.getMaxRec()); LimitedStarTable table = new LimitedStarTable(result, colInfos, execReport.parameters.getMaxRec(), thread);
// Copy the table on disk (or in memory if the table is short): // Copy the table on disk (or in memory if the table is short):
StarTable copyTable = StoragePolicy.PREFER_DISK.copyTable(table); StarTable copyTable = StoragePolicy.PREFER_DISK.copyTable(table);
if (thread.isInterrupted())
throw new InterruptedException();
/* Format the table in FITS (2 passes are needed for that, hence the copy on disk), /* Format the table in FITS (2 passes are needed for that, hence the copy on disk),
* and write it in the given output stream: */ * and write it in the given output stream: */
new FitsTableWriter().writeStarTable(copyTable, output); new FitsTableWriter().writeStarTable(copyTable, output);
if (thread.isInterrupted())
throw new InterruptedException();
execReport.nbRows = table.getNbReadRows(); execReport.nbRows = table.getNbReadRows();
output.flush(); output.flush();
......
...@@ -83,7 +83,7 @@ import adql.db.DBType.DBDatatype; ...@@ -83,7 +83,7 @@ import adql.db.DBType.DBDatatype;
* </p> * </p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.1 (07/2015) * @version 2.1 (11/2015)
*/ */
public class VOTableFormat implements OutputFormat { public class VOTableFormat implements OutputFormat {
...@@ -330,7 +330,7 @@ public class VOTableFormat implements OutputFormat { ...@@ -330,7 +330,7 @@ public class VOTableFormat implements OutputFormat {
ColumnInfo[] colInfos = toColumnInfos(queryResult, execReport, thread); ColumnInfo[] colInfos = toColumnInfos(queryResult, execReport, thread);
/* Turns the result set into a table. */ /* Turns the result set into a table. */
LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec()); LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec(), thread);
/* Prepares the object that will do the serialization work. */ /* Prepares the object that will do the serialization work. */
VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table); VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table);
...@@ -347,6 +347,9 @@ public class VOTableFormat implements OutputFormat { ...@@ -347,6 +347,9 @@ public class VOTableFormat implements OutputFormat {
execReport.nbRows = table.getNbReadRows(); execReport.nbRows = table.getNbReadRows();
out.flush(); out.flush();
if (thread.isInterrupted())
throw new InterruptedException();
/* Check for overflow and write INFO if required. */ /* Check for overflow and write INFO if required. */
if (table.lastSequenceOverflowed()){ if (table.lastSequenceOverflowed()){
out.write("<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\"/>"); out.write("<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\"/>");
...@@ -588,7 +591,7 @@ public class VOTableFormat implements OutputFormat { ...@@ -588,7 +591,7 @@ public class VOTableFormat implements OutputFormat {
* </p> * </p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (10/2014) * @version 2.1 (11/2015)
* @since 2.0 * @since 2.0
*/ */
public static class LimitedStarTable extends AbstractStarTable { public static class LimitedStarTable extends AbstractStarTable {
...@@ -602,6 +605,10 @@ public class VOTableFormat implements OutputFormat { ...@@ -602,6 +605,10 @@ public class VOTableFormat implements OutputFormat {
/** Iterator over the data to read using this special {@link StarTable} */ /** Iterator over the data to read using this special {@link StarTable} */
private final TableIterator tableIt; private final TableIterator tableIt;
/** Thread covering this execution. If it is interrupted, the writing must stop as soon as possible.
* @since 2.1 */
private final Thread threadToWatch;
/** Limit on the number of rows to read. Over this limit, an "overflow" event occurs and {@link #overflow} is set to TRUE. */ /** Limit on the number of rows to read. Over this limit, an "overflow" event occurs and {@link #overflow} is set to TRUE. */
private final long maxrec; private final long maxrec;
...@@ -620,9 +627,11 @@ public class VOTableFormat implements OutputFormat { ...@@ -620,9 +627,11 @@ public class VOTableFormat implements OutputFormat {
* @param tableIt Data on which to iterate using this special {@link StarTable}. * @param tableIt Data on which to iterate using this special {@link StarTable}.
* @param colInfos Information about all columns. * @param colInfos Information about all columns.
* @param maxrec Limit on the number of rows to read. <i>(if negative, there will be no limit)</i> * @param maxrec Limit on the number of rows to read. <i>(if negative, there will be no limit)</i>
* @param thread Parent thread. When an interruption is detected the writing must stop as soon as possible.
*/ */
LimitedStarTable(final TableIterator tableIt, final ColumnInfo[] colInfos, final long maxrec){ LimitedStarTable(final TableIterator tableIt, final ColumnInfo[] colInfos, final long maxrec, final Thread thread){
this.tableIt = tableIt; this.tableIt = tableIt;
this.threadToWatch = thread;
nbCol = colInfos.length; nbCol = colInfos.length;
columnInfos = colInfos; columnInfos = colInfos;
this.maxrec = maxrec; this.maxrec = maxrec;
...@@ -675,7 +684,7 @@ public class VOTableFormat implements OutputFormat { ...@@ -675,7 +684,7 @@ public class VOTableFormat implements OutputFormat {
public boolean next() throws IOException{ public boolean next() throws IOException{
irow++; irow++;
try{ try{
if (maxrec < 0 || irow < maxrec){ if (!threadToWatch.isInterrupted() && (maxrec < 0 || irow < maxrec)){
boolean hasNext = tableIt.nextRow(); boolean hasNext = tableIt.nextRow();
if (hasNext){ if (hasNext){
for(int i = 0; i < nbCol && tableIt.hasNextCol(); i++) for(int i = 0; i < nbCol && tableIt.hasNextCol(); i++)
......
...@@ -1320,7 +1320,7 @@ public class UWSJob extends SerializableUWSObject { ...@@ -1320,7 +1320,7 @@ public class UWSJob extends SerializableUWSObject {
// Set the end time: // Set the end time:
setEndTime(new Date()); setEndTime(new Date());
}else if (thread == null || (thread != null && !thread.isAlive())) }else if ((thread == null || (thread != null && !thread.isAlive())) && phase.getPhase() != ExecutionPhase.ABORTED)
throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(getJobId(), phase.getPhase(), ExecutionPhase.ABORTED)); throw new UWSException(UWSException.BAD_REQUEST, UWSExceptionFactory.incorrectPhaseTransition(getJobId(), phase.getPhase(), ExecutionPhase.ABORTED));
}else }else
getLogger().logJob(LogLevel.WARNING, this, "ABORT", "Abortion of the job \"" + getJobId() + "\" asked but not yet effective (after having waited " + waitForStop + "ms)!", null); getLogger().logJob(LogLevel.WARNING, this, "ABORT", "Abortion of the job \"" + getJobId() + "\" asked but not yet effective (after having waited " + waitForStop + "ms)!", null);
...@@ -1391,12 +1391,20 @@ public class UWSJob extends SerializableUWSObject { ...@@ -1391,12 +1391,20 @@ public class UWSJob extends SerializableUWSObject {
} }
/** /**
* Tells whether the thread is different from <i>null</i>, is not alive, is interrupted or is finished (see {@link JobThread#isFinished()}). * <p>Tells whether the thread is different from <i>null</i>, is not alive or is finished (see {@link JobThread#isFinished()}).</p>
*
* <p><i><b>Important note:</b>
* Having the interrupted flag set to <code>true</code> is not enough to consider the job as stopped.
* So, if the job has been interrupted but is still running, it should mean that the {@link JobThread#jobWork()} does not
* check the interrupted flag of the thread often enough or not at the right moments. In such case, the job can not be
* considered as stopped/aborted - so the phase stays {@link ExecutionPhase#EXECUTING EXECUTING} - until the thread is "unblocked"
* and the interruption is detected.
* </i></p>
* *
* @return <i>true</i> if the thread is not still running, <i>false</i> otherwise. * @return <i>true</i> if the thread is not still running, <i>false</i> otherwise.
*/ */
protected final boolean isStopped(){ protected final boolean isStopped(){
return thread == null || !thread.isAlive() || thread.isInterrupted() || thread.isFinished(); return thread == null || !thread.isAlive() || thread.isFinished();
} }
/** /**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment