Newer
Older
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-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
import java.util.Date;
import java.util.List;
import tap.log.TAPLog;
import tap.parameters.DALIUpload;
import tap.parameters.TAPParameters;
import uws.UWSException;
import uws.job.ErrorSummary;
import uws.job.ExecutionPhase;
import uws.job.JobThread;
import uws.job.Result;
import uws.job.UWSJob;
import uws.job.parameters.UWSParameters;
import uws.job.user.JobOwner;
import uws.service.log.UWSLog.LogLevel;
* <p>Description of a TAP job. This class is used for asynchronous but also synchronous queries.</p>
*
* <p>
* On the contrary to {@link UWSJob}, it is loading parameters from {@link TAPParameters} instances rather than {@link UWSParameters}.
* However, {@link TAPParameters} is an extension of {@link UWSParameters}. That's what allow the UWS library to use both {@link TAPJob} and {@link TAPParameters}.
* </p>
*
* @author Grégory Mantelet (CDS;ARI)
* @version 2.1 (01/2016)
public class TAPJob extends UWSJob {
private static final long serialVersionUID = 1L;
/** Name of the standard TAP parameter which specifies the type of request to execute: "REQUEST". */
public static final String PARAM_REQUEST = "request";
/** REQUEST value meaning an ADQL query must be executed: "doQuery". */
public static final String REQUEST_DO_QUERY = "doQuery";
/** REQUEST value meaning VO service capabilities must be returned: "getCapabilities". */
public static final String REQUEST_GET_CAPABILITIES = "getCapabilities";
/** Name of the standard TAP parameter which specifies the query language: "LANG". <i>(only the ADQL language is supported by default in this version of the library)</i> */
public static final String PARAM_LANGUAGE = "lang";
/** LANG value meaning ADQL language: "ADQL". */
public static final String LANG_ADQL = "ADQL";
/** LANG value meaning PQL language: "PQL". <i>(this language is not supported in this version of the library)</i> */
public static final String LANG_PQL = "PQL";
/** Name of the standard TAP parameter which specifies the version of the TAP protocol that must be used: "VERSION". <i>(only the version 1.0 is supported in this version of the library)</i> */
public static final String PARAM_VERSION = "version";
/** VERSION value meaning the version 1.0 of TAP: "1.0". */
public static final String VERSION_1_0 = "1.0";
/** Name of the standard TAP parameter which specifies the output format (format of a query result): "FORMAT". */
public static final String PARAM_FORMAT = "format";
/** FORMAT value meaning the VOTable format: "votable". */
public static final String FORMAT_VOTABLE = "votable";
/** Name of the standard TAP parameter which specifies the maximum number of rows that must be returned in the query result: "MAXREC". */
public static final String PARAM_MAX_REC = "maxRec";
/** Special MAXREC value meaning the number of output rows is not limited. */
public static final int UNLIMITED_MAX_REC = -1;
/** Name of the standard TAP parameter which specifies the query to execute: "QUERY". */
public static final String PARAM_QUERY = "query";
/** Name of the standard TAP parameter which defines the tables to upload in the database for the query execution: "UPLOAD". */
public static final String PARAM_UPLOAD = "upload";
/** Name of the library parameter which informs about a query execution progression: "PROGRESSION". <i>(this parameter is removed once the execution is finished)</i> */
public static final String PARAM_PROGRESSION = "progression";
/** Internal query execution report. */
protected TAPExecutionReport execReport = null;
/** Parameters of this job for its execution. */
protected final TAPParameters tapParams;
/**
* <p>Build a pending TAP job with the given parameters.</p>
*
* <p><i><u>Note:</u> if the parameter {@link #PARAM_PHASE} (</i>phase<i>) is given with the value {@link #PHASE_RUN}
* the job execution starts immediately after the job has been added to a job list or after {@link #applyPhaseParam(JobOwner)} is called.</i></p>
*
* @param owner User who owns this job. <i>MAY BE NULL</i>
* @param tapParams Set of parameters.
*
* @throws TAPException If one of the given parameters has a forbidden or wrong value.
*/
public TAPJob(final JobOwner owner, final TAPParameters tapParams) throws TAPException{
super(owner, tapParams);
this.tapParams = tapParams;
tapParams.check();
}
/**
* <p>Build a pending TAP job with the given parameters.
* The given HTTP request ID will be used as Job ID if not already used by another job.</p>
*
* <p><i><u>Note:</u> if the parameter {@link #PARAM_PHASE} (</i>phase<i>) is given with the value {@link #PHASE_RUN}
* the job execution starts immediately after the job has been added to a job list or after {@link #applyPhaseParam(JobOwner)} is called.</i></p>
*
* @param owner User who owns this job. <i>MAY BE NULL</i>
* @param tapParams Set of parameters.
* @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 #generateJobId()}.</i>
*
* @throws TAPException If one of the given parameters has a forbidden or wrong value.
*
* @since 2.1
*/
public TAPJob(final JobOwner owner, final TAPParameters tapParams, final String requestID) throws TAPException{
super(owner, tapParams, requestID);
this.tapParams = tapParams;
tapParams.check();
}
/**
* <p>Restore a job in a state defined by the given parameters.
* The phase must be set separately with {@link #setPhase(uws.job.ExecutionPhase, boolean)}, where the second parameter is true.</p>
*
* @param jobID ID of the job.
* @param owner User who owns this job.
* @param params Set of not-standard UWS parameters (i.e. what is called by {@link UWSJob} as additional parameters ; they includes all TAP parameters).
* @param quote Quote of this job.
* @param startTime Date/Time at which this job started. <i>(if not null, it means the job execution was finished, so a endTime should be provided)</i>
* @param endTime Date/Time at which this job finished.
* @param results List of results. <i>NULL if the job has not been executed, has been aborted or finished with an error.</i>
* @param error Error with which this job ends.
*
* @throws TAPException If one of the given parameters has a forbidden or wrong value.
*/
public TAPJob(final String jobID, final JobOwner owner, final TAPParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws TAPException{
super(jobID, owner, params, quote, startTime, endTime, results, error);
this.tapParams = params;
this.tapParams.check();
}
/**
* Get the object storing and managing the set of all (UWS and TAP) parameters.
*
* @return The object managing all job parameters.
gmantele
committed
public final TAPParameters getTapParams(){
return tapParams;
}
/**
* <p>Get the value of the REQUEST parameter.</p>
*
* <p>This value must be {@value #REQUEST_DO_QUERY}.</p>
*
* @return REQUEST value.
*/
public final String getRequest(){
return tapParams.getRequest();
}
/**
* Get the value of the FORMAT parameter.
*
* @return FORMAT value.
*/
public final String getFormat(){
return tapParams.getFormat();
}
/**
* <p>Get the value of the LANG parameter.</p>
*
* <p>This value should always be {@value #LANG_ADQL} in this version of the library</p>
*
* @return LANG value.
*/
public final String getLanguage(){
return tapParams.getLang();
}
/**
* <p>Get the value of the MAXREC parameter.</p>
*
* <p>If this value is negative, it means the number of output rows is not limited.</p>
*
* @return MAXREC value.
*/
public final int getMaxRec(){
return tapParams.getMaxRec();
}
/**
* Get the value of the QUERY parameter (i.e. the query, in the language returned by {@link #getLanguage()}, to execute).
*
* @return QUERY value.
*/
public final String getQuery(){
return tapParams.getQuery();
}
/**
* <p>Get the value of the VERSION parameter.</p>
*
* <p>This value should be {@value #VERSION_1_0} in this version of the library.</p>
*
* @return VERSION value.
*/
public final String getVersion(){
return tapParams.getVersion();
}
/**
* <p>Get the value of the UPLOAD parameter.</p>
*
* <p>This value must be formatted as specified by the TAP standard (= a semicolon separated list of DALI uploads).</p>
*
* @return UPLOAD value.
*/
public final String getUpload(){
return tapParams.getUpload();
}
/**
* <p>Get the list of tables to upload in the database for the query execution.</p>
*
* <p>The returned array is an interpretation of the UPLOAD parameter.</p>
*
* @return List of tables to upload.
*/
public final DALIUpload[] getTablesToUpload(){
return tapParams.getUploadedTables();
* <p>Get the execution report.</p>
*
* <p>
* This report is available only during or after the job execution.
* It tells in which step the execution is, and how long was the previous steps.
* It can also give more information about the number of resulting rows and columns.
* </p>
*
* @return The execReport.
*/
gmantele
committed
public final TAPExecutionReport getExecReport(){
return execReport;
}
/**
* <p>Set the execution report.</p>
*
* <p><b>IMPORTANT:
* This function can be called only if the job is running or is being restored, otherwise an exception would be thrown.
* It should not be used by implementors, but only by the internal library processing.
* </b></p>
*
* @param execReport An execution report.
*
* @throws UWSException If this job has never been restored and is not running.
public final void setExecReport(final TAPExecutionReport execReport) throws UWSException{
if (getRestorationDate() == null && (thread == null || thread.isFinished()))
gmantele
committed
throw new UWSException("Impossible to set an execution report if the job is not in the EXECUTING phase ! Here, the job \"" + jobId + "\" is in the phase " + getPhase());
this.execReport = execReport;
}
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/**
* <p>Create the thread to use for the execution of this job.</p>
*
* <p><i>Note: If the job already exists, this function does nothing.</i></p>
*
* @throws NullPointerException If the factory returned NULL rather than the asked {@link JobThread}.
* @throws UWSException If the thread creation fails.
*
* @see TAPFactory#createJobThread(UWSJob)
*
* @since 2.0
*/
private final void createThread() throws NullPointerException, UWSException{
if (thread == null){
thread = getFactory().createJobThread(this);
if (thread == null)
throw new NullPointerException("Missing job work! The thread created by the factory is NULL => The job can't be executed!");
}
}
/**
* <p>Check whether this job is able to start right now.</p>
*
* <p>
* Basically, this function try to get a database connection. If none is available,
* then this job can not start and this function return FALSE. In all the other cases,
* TRUE is returned.
* </p>
*
* <p><b>Warning:</b> This function will indirectly open and keep a database connection, so that the job can be started just after its call.
* If it turns out that the execution won't start just after this call, the DB connection should be closed in some way in order to save database resources.</i></p>
*
* @return <i>true</i> if this job can start right now, <i>false</i> otherwise.
*
* @since 2.0
*/
public final boolean isReadyForExecution(){
return thread != null && ((AsyncThread)thread).isReadyForExecution();
}
@Override
public final void start(final boolean useManager) throws UWSException{
// This job must know its jobs list and this jobs list must know its UWS:
if (getJobList() == null || getJobList().getUWS() == null)
throw new IllegalStateException("A TAPJob can not start if it is not linked to a job list or if its job list is not linked to a UWS.");
// If already running do nothing:
else if (isRunning())
return;
// If asked propagate this request to the execution manager:
else if (useManager){
// Create its corresponding thread, if not already existing:
createThread();
// Ask to the execution manager to test whether the job is ready for execution, and if, execute it (by calling this function with "false" as parameter):
getJobList().getExecutionManager().execute(this);
}// Otherwise start directly the execution:
else{
// Create its corresponding thread, if not already existing:
createThread();
if (!isReadyForExecution()){
UWSException ue = new NoDBConnectionAvailableException();
((TAPLog)getLogger()).logDB(LogLevel.ERROR, null, "CONNECTION_LACK", "No more database connection available for the moment!", ue);
getLogger().logJob(LogLevel.ERROR, this, "ERROR", "Asynchronous job " + jobId + " execution aborted: no database connection available!", null);
throw ue;
}
// Change the job phase:
setPhase(ExecutionPhase.EXECUTING);
// Set the start time:
setStartTime(new Date());
// Run the job:
thread.start();
(new JobTimeOut()).start();
// Log the start of this job:
getLogger().logJob(LogLevel.INFO, this, "START", "Job \"" + jobId + "\" started.", null);
}
}
/** @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.
*
* @author Grégory Mantelet (ARI)
* @version 2.0 (02/2015)
* @since 2.0
*/
public static class NoDBConnectionAvailableException extends UWSException {
private static final long serialVersionUID = 1L;
public NoDBConnectionAvailableException(){
super("Service momentarily too busy! Please try again later.");
}
}