diff --git a/src/tap/TAPSyncJob.java b/src/tap/TAPSyncJob.java index abcbb59de2a2f3518804884cf60dda70839bfca5..51878fda5cf8c9d42628cef148abf97852a23951 100644 --- a/src/tap/TAPSyncJob.java +++ b/src/tap/TAPSyncJob.java @@ -2,26 +2,27 @@ package tap; /* * 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 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; import java.util.Date; +import java.util.Iterator; import javax.servlet.http.HttpServletResponse; @@ -29,24 +30,25 @@ import tap.parameters.TAPParameters; import uws.UWSException; import uws.job.JobThread; import uws.service.log.UWSLog.LogLevel; +import uws.service.request.UploadFile; /** * <p>This class represent a TAP synchronous job. * A such job must execute an ADQL query and return immediately its result.</p> - * + * * <h3>Timeout</h3> - * + * * <p> * The execution of a such job is limited to a short time. Once this time elapsed, the job is stopped. * For a longer job, an asynchronous job should be used. * </p> - * + * * <h3>Error management</h3> - * + * * <p> * If an error occurs it must be propagated ; it will be written later in the HTTP response on a top level. * </p> - * + * * @author Grégory Mantelet (CDS;ARI) * @version 2.1 (03/2017) */ @@ -74,17 +76,17 @@ public class TAPSyncJob { protected TAPExecutionReport execReport = null; /** Date at which this synchronous job has really started. It is NULL when the job has never been started. - * + * * <p><i>Note: A synchronous job can be run just once ; so if an attempt of executing it again, the start date will be tested: * if NULL, the second starting is not considered and an exception is thrown.</i></p> */ private Date startedAt = null; /** * Create a synchronous TAP job. - * + * * @param service Description of the TAP service which is in charge of this synchronous job. * @param params Parameters of the query to execute. It must mainly contain the ADQL query to execute. - * + * * @throws NullPointerException If one of the parameters is NULL. */ public TAPSyncJob(final ServiceConnection service, final TAPParameters params) throws NullPointerException{ @@ -103,14 +105,14 @@ public class TAPSyncJob { /** * Create a synchronous TAP job. * The given HTTP request ID will be used as Job ID if not already used by another job. - * + * * @param service Description of the TAP service which is in charge of this synchronous job. * @param params Parameters of the query to execute. It must mainly contain the ADQL query to execute. * @param requestID ID of the HTTP request which has initiated the creation of this job. * <i>Note: if NULL, empty or already used, a job ID will be generated thanks to {@link #generateId()}.</i> - * + * * @throws NullPointerException If one of the 2 first parameters is NULL. - * + * * @since 2.1 */ public TAPSyncJob(final ServiceConnection service, final TAPParameters params, final String requestID) throws NullPointerException{ @@ -135,13 +137,13 @@ public class TAPSyncJob { /** * <p>This function lets generating a unique ID.</p> - * + * * <p><i><b>By default:</b> "S"+System.currentTimeMillis()+UpperCharacter (UpperCharacter: one upper-case character: A, B, C, ....)</i></p> - * + * * <p><i><u>note: </u> DO NOT USE in this function any of the following functions: {@link ServiceConnection#getLogger()}, * {@link ServiceConnection#getFileManager()} and {@link ServiceConnection#getFactory()}. All of them will return NULL, because this job does not * yet know its jobs list (which is needed to know the UWS and so, all of the objects returned by these functions).</i></p> - * + * * @return A unique job identifier. */ protected String generateId(){ @@ -158,7 +160,7 @@ public class TAPSyncJob { /** * Get the ID of this synchronous job. - * + * * @return The job ID. */ public final String getID(){ @@ -167,7 +169,7 @@ public class TAPSyncJob { /** * Get the TAP parameters provided by the user and which will be used for the execution of this job. - * + * * @return Job parameters. */ public final TAPParameters getTapParams(){ @@ -177,7 +179,7 @@ public class TAPSyncJob { /** * Get the report of the execution of this job. * This report is NULL if the execution has not yet started. - * + * * @return Report of this job execution. */ public final TAPExecutionReport getExecReport(){ @@ -186,21 +188,21 @@ public class TAPSyncJob { /** * <p>Start the execution of this job in order to execute the given ADQL query.</p> - * + * * <p>The execution itself will be processed by an {@link ADQLExecutor} inside a thread ({@link SyncThread}).</p> - * + * * <p><b>Important:</b> * No error should be written in this function. If any error occurs it should be thrown, in order to be manager on a top level. * </p> - * + * * @param response Response in which the result must be written. - * + * * @return <i>true</i> if the execution was successful, <i>false</i> otherwise. - * + * * @throws IllegalStateException If this synchronous job has already been started before. * @throws IOException If any error occurs while writing the query result in the given {@link HttpServletResponse}. * @throws TAPException If any error occurs while executing the ADQL query. - * + * * @see SyncThread */ public synchronized boolean start(final HttpServletResponse response) throws IllegalStateException, IOException, TAPException{ @@ -240,6 +242,9 @@ public class TAPSyncJob { }finally{ // Whatever the way the execution stops (normal, cancel or error), an execution report must be fulfilled: execReport = thread.getExecutionReport(); + + // Delete uploaded files: + deleteUploads(tapParams); } // Report any error that may have occurred while the thread execution: @@ -296,14 +301,33 @@ public class TAPSyncJob { return thread.isSuccess(); } + /** + * Delete all uploaded files. + * + * @param tapParams Input parameters (listing all uploaded files, if any). + * + * @since 2.3 + */ + protected void deleteUploads(final TAPParameters tapParams){ + Iterator<UploadFile> itFiles = tapParams.getFiles(); + while(itFiles.hasNext()){ + UploadFile uf = itFiles.next(); + try{ + uf.deleteFile(); + }catch(IOException ioe){ + service.getLogger().logTAP(LogLevel.WARNING, this, "END", "Unable to delete the uploaded file \"" + uf.getLocation() + "\"!", ioe); + } + } + } + /** * <p>Thread which will process the job execution.</p> - * + * * <p> * Actually, it will basically just call {@link ADQLExecutor#start(Thread, String, TAPParameters, HttpServletResponse)} * with the given {@link ADQLExecutor} and TAP parameters (containing the ADQL query to execute). * </p> - * + * * @author Grégory Mantelet (CDS;ARI) * @version 2.1 (03/2017) */ @@ -326,7 +350,7 @@ public class TAPSyncJob { /** * Create a thread that will run the given executor with the given parameters. - * + * * @param executor Object to execute and which knows how to execute an ADQL query. * @param ID ID of the synchronous job owning this thread. * @param tapParams TAP parameters to use to get the query to execute and the execution parameters. @@ -342,7 +366,7 @@ public class TAPSyncJob { /** * Tell whether the execution has ended with success. - * + * * @return <i>true</i> if the query has been successfully executed, * <i>false</i> otherwise (or if this thread is still executed). */ @@ -353,7 +377,7 @@ public class TAPSyncJob { /** * Get the error that has interrupted/stopped this thread. * This function returns NULL if the query has been successfully executed. - * + * * @return Error that occurs while executing the query * or NULL if the execution was a success. */ @@ -363,7 +387,7 @@ public class TAPSyncJob { /** * Get the report of the query execution. - * + * * @return Query execution report. */ public final TAPExecutionReport getExecutionReport(){ diff --git a/src/tap/upload/Uploader.java b/src/tap/upload/Uploader.java index 27f7d8124f2549248a4f1efe927a4aab63eb49e3..5abd21f3cf56312e1c8a3ee4f07667e7d49710cd 100644 --- a/src/tap/upload/Uploader.java +++ b/src/tap/upload/Uploader.java @@ -2,27 +2,29 @@ package tap.upload; /* * 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 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * + * Copyright 2012-2018 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; import java.io.InputStream; +import com.oreilly.servlet.multipart.ExceededSizeException; + import tap.ServiceConnection; import tap.ServiceConnection.LimitUnit; import tap.TAPException; @@ -31,6 +33,7 @@ import tap.data.LimitedTableIterator; import tap.data.TableIterator; import tap.data.VOTableIterator; import tap.db.DBConnection; +import tap.db.DBException; import tap.metadata.TAPColumn; import tap.metadata.TAPMetadata; import tap.metadata.TAPMetadata.STDSchema; @@ -40,33 +43,34 @@ import tap.parameters.DALIUpload; import uws.UWSException; import uws.service.file.UnsupportedURIProtocolException; -import com.oreilly.servlet.multipart.ExceededSizeException; - /** - * <p>Let create properly given VOTable inputs in the "database".</p> - * + * Let create properly given VOTable inputs in the "database". + * * <p> - * This class manages particularly the upload limit in rows and in bytes by creating a {@link LimitedTableIterator} - * with a {@link VOTableIterator}. + * This class manages particularly the upload limit in rows and in bytes by + * creating a {@link LimitedTableIterator} with a {@link VOTableIterator}. * </p> - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (04/2015) - * + * @version 2.3 (08/2018) + * * @see LimitedTableIterator * @see VOTableIterator */ public class Uploader { /** Specification of the TAP service. */ protected final ServiceConnection service; - /** Connection to the "database" (which lets upload the content of any given VOTable). */ + /** Connection to the "database" (which lets upload the content of any given + * VOTable). */ protected final DBConnection dbConn; /** Description of the TAP_UPLOAD schema to use. * @since 2.0 */ protected final TAPSchema uploadSchema; - /** Type of limit to set: ROWS or BYTES. <i>MAY be NULL ; if NULL, no limit will be set.</i> */ + /** Type of limit to set: ROWS or BYTES. <i>MAY be NULL ; if NULL, no limit + * will be set.</i> */ protected final LimitUnit limitUnit; - /** Limit on the number of rows or bytes (depending of {@link #limitUnit}) allowed to be uploaded in once (whatever is the number of tables). */ + /** Limit on the number of rows or bytes (depending of {@link #limitUnit}) + * allowed to be uploaded in once (whatever is the number of tables). */ protected final int limit; /** Number of rows already loaded. */ @@ -74,11 +78,12 @@ public class Uploader { /** * Build an {@link Uploader} object. - * + * * @param service Specification of the TAP service using this uploader. * @param dbConn A valid (open) connection to the "database". - * - * @throws TAPException If any error occurs while building this {@link Uploader}. + * + * @throws TAPException If any error occurs while building this + * {@link Uploader}. */ public Uploader(final ServiceConnection service, final DBConnection dbConn) throws TAPException{ this(service, dbConn, null); @@ -86,12 +91,13 @@ public class Uploader { /** * Build an {@link Uploader} object. - * + * * @param service Specification of the TAP service using this uploader. * @param dbConn A valid (open) connection to the "database". - * - * @throws TAPException If any error occurs while building this {@link Uploader}. - * + * + * @throws TAPException If any error occurs while building this + * {@link Uploader}. + * * @since 2.0 */ public Uploader(final ServiceConnection service, final DBConnection dbConn, final TAPSchema uplSchema) throws TAPException{ @@ -131,20 +137,31 @@ public class Uploader { } /** - * <p>Upload all the given VOTable inputs.</p> - * - * <p><i>Note: - * The {@link TAPTable} objects representing the uploaded tables will be associated with the TAP_UPLOAD schema specified at the creation of this {@link Uploader}. - * If no such schema was specified, a default one (whose DB name will be equals to the ADQL name, that's to say {@link STDSchema#UPLOADSCHEMA}) - * is created, will be associated with the uploaded tables and will be returned by this function. - * </i></p> - * + * Upload all the given VOTable inputs. + * + * <p><b>Note 1:</b> + * The {@link TAPTable} objects representing the uploaded tables will be + * associated with the TAP_UPLOAD schema specified at the creation of this + * {@link Uploader}. If no such schema was specified, a default one (whose + * DB name will be equals to the ADQL name, that's to say + * {@link STDSchema#UPLOADSCHEMA}) is created, will be associated with the + * uploaded tables and will be returned by this function. + * </p> + * + * <p><b>Note 2:</b> + * In case of error while ingesting one or all of the uploaded tables, + * all tables created in the database before the error occurs are dropped + * <i>(see {@link #dropUploadedTables()})</i>. + * </p> + * * @param uploads Array of tables to upload. - * - * @return A {@link TAPSchema} containing the list and the description of all uploaded tables. - * - * @throws TAPException If any error occurs while reading the VOTable inputs or while uploading the table into the "database". - * + * + * @return A {@link TAPSchema} containing the list and the description of + * all uploaded tables. + * + * @throws TAPException If any error occurs while reading the VOTable inputs + * or while uploading the table into the "database". + * * @see DBConnection#addUploadedTable(TAPTable, tap.data.TableIterator) */ public TAPSchema upload(final DALIUpload[] uploads) throws TAPException{ @@ -181,14 +198,28 @@ public class Uploader { votable = null; } }catch(DataReadException dre){ + // Drop uploaded tables: + dropUploadedTables(); + // Report the error: if (dre.getCause() instanceof ExceededSizeException) throw dre; else throw new TAPException("Error while reading the VOTable \"" + tableName + "\": " + dre.getMessage(), dre, UWSException.BAD_REQUEST); }catch(IOException ioe){ + // Drop uploaded tables: + dropUploadedTables(); + // Report the error: throw new TAPException("IO error while reading the VOTable of \"" + tableName + "\"!", ioe); }catch(UnsupportedURIProtocolException e){ + // Drop uploaded tables: + dropUploadedTables(); + // Report the error: throw new TAPException("URI error while trying to open the VOTable of \"" + tableName + "\"!", e); + }catch(TAPException te){ + // Drop uploaded tables: + dropUploadedTables(); + // Report the error: + throw te; }finally{ try{ if (dataIt != null) @@ -200,8 +231,27 @@ public class Uploader { } } - // Return the TAP_UPLOAD schema (containing just the description of the uploaded tables): + /* Return the TAP_UPLOAD schema (containing just the description of the + * uploaded tables): */ return uploadSchema; } + /** + * Drop all tables already uploaded in the database. + * + * @since 2.3 + */ + protected void dropUploadedTables(){ + if (uploadSchema == null || uploadSchema.getNbTables() == 0) + return; + + for(TAPTable table : uploadSchema){ + try{ + dbConn.dropUploadedTable(table); + }catch(DBException e){ + service.getLogger().error("Unable to drop the uploaded table " + table.getFullName() + "!", e); + } + } + } + }