Commit e7dff888 authored by gmantele's avatar gmantele
Browse files

[TAP] Add a new database access method in the configuration file: get a...

[TAP] Add a new database access method in the configuration file: get a Datasource from JNDI. & Better support of connection pooling (TAPFactory.countFreeConnections() has been removed ; when the creation of a database connection fails with an SQLException, it is considered that no connection are momentarily available...async jobs will be queued and all sync requests will be rejected).
parent 1986ed0e
Loading
Loading
Loading
Loading
+20 −1
Original line number Diff line number Diff line
@@ -228,6 +228,25 @@ public class ADQLExecutor {
		}
	}

	/**
	 * <p>Create the database connection required for the ADQL execution.</p>
	 * 
	 * <p><i>Note: This function has no effect if the DB connection already exists.</i></p>
	 * 
	 * @param jobID	ID of the job which will be executed by this {@link ADQLExecutor}.
	 *             	This ID will be the database connection ID.
	 * 
	 * @throws TAPException	If the DB connection creation fails.
	 * 
	 * @see TAPFactory#getConnection(String)
	 * 
	 * @since 2.0
	 */
	public final void initDBConnection(final String jobID) throws TAPException{
		if (dbConn == null)
			dbConn = service.getFactory().getConnection(jobID);
	}

	/**
	 * <p>Start the synchronous processing of the ADQL query.</p>
	 * 
@@ -296,7 +315,7 @@ public class ADQLExecutor {

		try{
			// Get a "database" connection:
			dbConn = service.getFactory().getConnection(report.jobID);
			initDBConnection(report.jobID);

			// 1. UPLOAD TABLES, if there is any:
			if (tapParams.getUploadedTables() != null && tapParams.getUploadedTables().length > 0){
+29 −3
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package tap;
 * You should have received a copy of the GNU Lesser General Public License
 * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institut (ARI)
 */

@@ -28,7 +28,7 @@ import uws.service.error.ServiceErrorWriter;
 * Thread in charge of a TAP job execution.
 * 
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.0 (09/2014)
 * @version 2.0 (02/2015)
 */
public class AsyncThread extends JobThread {

@@ -42,13 +42,39 @@ public class AsyncThread extends JobThread {
	 * @param executor		The object to use for the ADQL execution itself.
	 * @param errorWriter	The object to use to format and to write an execution error for the user.
	 * 
	 * @throws NullPointerException	If the job parameter is missing.
	 * @throws NullPointerException	If the job parameter or the {@link ADQLExecutor} is missing.
	 */
	public AsyncThread(final TAPJob j, final ADQLExecutor executor, final ServiceErrorWriter errorWriter) throws NullPointerException{
		super(j, "Execute the ADQL query of the TAP request " + j.getJobId(), errorWriter);
		if (executor == null)
			throw new NullPointerException("Missing ADQLExecutor! Can not create an instance of AsyncThread without.");
		this.executor = executor;
	}

	/**
	 * <p>Check whether this thread is able to start right now.</p>
	 * 
	 * <p>
	 * 	Basically, this function asks to the {@link ADQLExecutor} to get a database connection. If no DB connection is available,
	 * 	then this thread 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 thread can start right now, <i>false</i> otherwise.
	 * 
	 * @since 2.0
	 */
	public final boolean isReadyForExecution(){
		try{
			executor.initDBConnection(job.getJobId());
			return true;
		}catch(TAPException te){
			return false;
		}
	}

	@Override
	protected void jobWork() throws UWSException, InterruptedException{
		try{
+0 −23
Original line number Diff line number Diff line
@@ -149,29 +149,6 @@ public abstract class TAPFactory implements UWSFactory {
	 */
	public abstract void freeConnection(final DBConnection conn);

	/**
	 * <p>Count the number of connection not currently used and available on demand.</p>
	 * 
	 * <p>This function is called particularly by the queue manager in order to determine whether a job can start.
	 * It won't start if no connection is available.</p>
	 * 
	 * <p><i><b>Important note:</b>
	 * 	If the implementation of this factory creates connections on the fly, the value 2 (or bigger) must always be returned.
	 * 	However, if the connections are managed by a connection pool, the count value must be asked to it.
	 * </i></p>
	 * 
	 * <p><i>Note:
	 * 	In case of error when counting, a null or negative value must be returned. If the error must be
	 * 	reported, it is up to this function to log the error before returning a null or negative value.
	 * </i></p>
	 * 
	 * @return	The number of connections still available,
	 *        	or <=0 in case of problem (<i>note: in this case, the error must be logged in the implementation of this function</i>).
	 * 
	 * @since 2.0
	 */
	public abstract int countFreeConnections();

	/**
	 * <p>Destroy all resources (and particularly DB connections and JDBC driver) allocated in this factory.</p>
	 * 
+104 −0
Original line number Diff line number Diff line
@@ -20,16 +20,21 @@ package tap;
 *                       Astronomisches Rechen Institut (ARI)
 */

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>
@@ -253,4 +258,103 @@ public class TAPJob extends UWSJob {
		this.execReport = execReport;
	}

	/**
	 * <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);
		}
	}

	/**
	 * This exception is thrown by a job execution when no database connection are available anymore.
	 * 
	 * @author Gr&eacute;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.");
		}

	}

}
+8 −1
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ import uws.service.log.UWSLog.LogLevel;
 * </p>
 * 
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 2.0 (12/2014)
 * @version 2.0 (02/2015)
 */
public class TAPSyncJob {

@@ -175,6 +175,13 @@ public class TAPSyncJob {

		// Create the object having the knowledge about how to execute an ADQL query:
		ADQLExecutor executor = service.getFactory().createADQLExecutor();
		try{
			executor.initDBConnection(ID);
		}catch(TAPException te){
			service.getLogger().logDB(LogLevel.ERROR, null, "CONNECTION_LACK", "No more database connection available for the moment!", te);
			service.getLogger().logTAP(LogLevel.ERROR, this, "END_EXEC", "Synchronous job " + ID + " execution aborted: no database connection available!", null);
			throw new TAPException("TAP service too busy! No connection available for the moment. You should try later or create an asynchronous query (which will be executed when enough resources will be available again).", UWSException.SERVICE_UNAVAILABLE);
		}

		// Give to a thread which will execute the query:
		thread = new SyncThread(executor, ID, tapParams, response);
Loading