diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java index 9143dca9dac68a46f85d0e07543bb8c9296fff47..3e19d11872ee5579f35fb9d0bdd0d1cdff88d934 100644 --- a/src/tap/ADQLExecutor.java +++ b/src/tap/ADQLExecutor.java @@ -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){ diff --git a/src/tap/AsyncThread.java b/src/tap/AsyncThread.java index deb7a3dfe65aa62a62f99fbe85610c3268cd4410..b97131a13f516fb8293c944f6f4bf06c34ff4640 100644 --- a/src/tap/AsyncThread.java +++ b/src/tap/AsyncThread.java @@ -16,7 +16,7 @@ package tap; * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012,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é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{ diff --git a/src/tap/TAPFactory.java b/src/tap/TAPFactory.java index 580e232831db6aebb92c5bc738dd0b7dd749c435..6dbc9a84209c0f76986dd7ebd94af6abce8b3a99 100644 --- a/src/tap/TAPFactory.java +++ b/src/tap/TAPFactory.java @@ -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> * diff --git a/src/tap/TAPJob.java b/src/tap/TAPJob.java index ab95375bb749cff6dc22704fcfb6ef0604921d09..597d24eeee1fc0cb71960f7a3745eff4190a07df 100644 --- a/src/tap/TAPJob.java +++ b/src/tap/TAPJob.java @@ -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é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."); + } + + } + } diff --git a/src/tap/TAPSyncJob.java b/src/tap/TAPSyncJob.java index 7ea795ece50b6393480fdcb10eb774fccc1fea1a..78411c71d280a48c0f1315a491c40bc3a17272c3 100644 --- a/src/tap/TAPSyncJob.java +++ b/src/tap/TAPSyncJob.java @@ -47,7 +47,7 @@ import uws.service.log.UWSLog.LogLevel; * </p> * * @author Gré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); diff --git a/src/tap/config/DefaultServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java similarity index 99% rename from src/tap/config/DefaultServiceConnection.java rename to src/tap/config/ConfigurableServiceConnection.java index f04733aacbcacc2a124658dcdf5d97011593d81b..1b78ae27c4c7e9545bfa2d10164638a0c7d45b96 100644 --- a/src/tap/config/DefaultServiceConnection.java +++ b/src/tap/config/ConfigurableServiceConnection.java @@ -70,13 +70,13 @@ import adql.db.FunctionDef; import adql.parser.ParseException; import adql.query.operand.function.UserDefinedFunction; -public final class DefaultServiceConnection implements ServiceConnection { +public final class ConfigurableServiceConnection implements ServiceConnection { private UWSFileManager fileManager; private TAPLog logger; - private DefaultTAPFactory tapFactory; + private ConfigurableTAPFactory tapFactory; private final TAPMetadata metadata; @@ -108,7 +108,7 @@ public final class DefaultServiceConnection implements ServiceConnection { private Collection<FunctionDef> udfs = new ArrayList<FunctionDef>(0); - public DefaultServiceConnection(final Properties tapConfig) throws NullPointerException, TAPException, UWSException{ + public ConfigurableServiceConnection(final Properties tapConfig) throws NullPointerException, TAPException, UWSException{ // 1. INITIALIZE THE FILE MANAGER: initFileManager(tapConfig); @@ -116,7 +116,7 @@ public final class DefaultServiceConnection implements ServiceConnection { logger = new DefaultTAPLog(fileManager); // 3. BUILD THE TAP FACTORY: - tapFactory = new DefaultTAPFactory(this, tapConfig); + tapFactory = new ConfigurableTAPFactory(this, tapConfig); // 4. GET THE METADATA: metadata = initMetadata(tapConfig); diff --git a/src/tap/config/DefaultTAPFactory.java b/src/tap/config/ConfigurableTAPFactory.java similarity index 55% rename from src/tap/config/DefaultTAPFactory.java rename to src/tap/config/ConfigurableTAPFactory.java index 1242917b6f8d074ce89c939c92d547806dee1c7a..f35fc752f510bee13d5840e315fc18d134d72b09 100644 --- a/src/tap/config/DefaultTAPFactory.java +++ b/src/tap/config/ConfigurableTAPFactory.java @@ -4,12 +4,16 @@ import static tap.config.TAPConfiguration.DEFAULT_BACKUP_BY_USER; import static tap.config.TAPConfiguration.DEFAULT_BACKUP_FREQUENCY; import static tap.config.TAPConfiguration.KEY_BACKUP_BY_USER; import static tap.config.TAPConfiguration.KEY_BACKUP_FREQUENCY; +import static tap.config.TAPConfiguration.KEY_DATABASE_ACCESS; +import static tap.config.TAPConfiguration.KEY_DATASOURCE_JNDI_NAME; import static tap.config.TAPConfiguration.KEY_DB_PASSWORD; import static tap.config.TAPConfiguration.KEY_DB_USERNAME; import static tap.config.TAPConfiguration.KEY_JDBC_DRIVER; import static tap.config.TAPConfiguration.KEY_JDBC_URL; import static tap.config.TAPConfiguration.KEY_SQL_TRANSLATOR; +import static tap.config.TAPConfiguration.VALUE_JDBC; import static tap.config.TAPConfiguration.VALUE_JDBC_DRIVERS; +import static tap.config.TAPConfiguration.VALUE_JNDI; import static tap.config.TAPConfiguration.VALUE_PGSPHERE; import static tap.config.TAPConfiguration.VALUE_POSTGRESQL; import static tap.config.TAPConfiguration.VALUE_USER_ACTION; @@ -19,6 +23,10 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; + import tap.AbstractTAPFactory; import tap.ServiceConnection; import tap.TAPException; @@ -32,10 +40,12 @@ import adql.translator.JDBCTranslator; import adql.translator.PgSphereTranslator; import adql.translator.PostgreSQLTranslator; -public final class DefaultTAPFactory extends AbstractTAPFactory { +public final class ConfigurableTAPFactory extends AbstractTAPFactory { private Class<? extends JDBCTranslator> translator; + private final DataSource datasource; + private final String driverPath; private final String dbUrl; private final String dbUser; @@ -44,66 +54,102 @@ public final class DefaultTAPFactory extends AbstractTAPFactory { private boolean backupByUser; private long backupFrequency; - public DefaultTAPFactory(ServiceConnection service, final Properties tapConfig) throws NullPointerException, TAPException{ + public ConfigurableTAPFactory(ServiceConnection service, final Properties tapConfig) throws NullPointerException, TAPException{ super(service); - /* 0. Extract the DB type and deduce the JDBC Driver path */ - String jdbcDriver = getProperty(tapConfig, KEY_JDBC_DRIVER); - String dbUrl = getProperty(tapConfig, KEY_JDBC_URL); - if (jdbcDriver == null){ - if (dbUrl == null) - throw new TAPException("The property \"" + KEY_JDBC_URL + "\" is missing!"); - else if (!dbUrl.startsWith(JDBCConnection.JDBC_PREFIX + ":")) - throw new TAPException("JDBC URL format incorrect! It MUST begins with " + JDBCConnection.JDBC_PREFIX + ":"); - else{ - String dbType = dbUrl.substring(JDBCConnection.JDBC_PREFIX.length() + 1); - if (dbType.indexOf(':') <= 0) - throw new TAPException("JDBC URL format incorrect! Database type name is missing."); - dbType = dbType.substring(0, dbType.indexOf(':')); - - jdbcDriver = VALUE_JDBC_DRIVERS.get(dbType); - if (jdbcDriver == null) - throw new TAPException("No JDBC driver known for the DBMS \"" + dbType + "\"!"); + /* 1. Configure the database access */ + final String dbAccessMethod = getProperty(tapConfig, KEY_DATABASE_ACCESS); + + // Case a: Missing access method => error! + if (dbAccessMethod == null) + throw new TAPException("The property \"" + KEY_DATABASE_ACCESS + "\" is missing! It is required to connect to the database. Two possible values: \"" + VALUE_JDBC + "\" and \"" + VALUE_JNDI + "\"."); + + // Case b: JDBC ACCESS + else if (dbAccessMethod.equalsIgnoreCase(VALUE_JDBC)){ + // Extract the DB type and deduce the JDBC Driver path: + String jdbcDriver = getProperty(tapConfig, KEY_JDBC_DRIVER); + String dbUrl = getProperty(tapConfig, KEY_JDBC_URL); + if (jdbcDriver == null){ + if (dbUrl == null) + throw new TAPException("The property \"" + KEY_JDBC_URL + "\" is missing! Since the choosen database access method is \"" + VALUE_JDBC + "\", this property is required."); + else if (!dbUrl.startsWith(JDBCConnection.JDBC_PREFIX + ":")) + throw new TAPException("JDBC URL format incorrect! It MUST begins with " + JDBCConnection.JDBC_PREFIX + ":"); + else{ + String dbType = dbUrl.substring(JDBCConnection.JDBC_PREFIX.length() + 1); + if (dbType.indexOf(':') <= 0) + throw new TAPException("JDBC URL format incorrect! Database type name is missing."); + dbType = dbType.substring(0, dbType.indexOf(':')); + + jdbcDriver = VALUE_JDBC_DRIVERS.get(dbType); + if (jdbcDriver == null) + throw new TAPException("No JDBC driver known for the DBMS \"" + dbType + "\"!"); + } + } + // Set the DB connection parameters: + this.driverPath = jdbcDriver; + this.dbUrl = dbUrl; + this.dbUser = getProperty(tapConfig, KEY_DB_USERNAME); + this.dbPassword = getProperty(tapConfig, KEY_DB_PASSWORD); + // Set the other DB connection parameters: + this.datasource = null; + } + // Case c: JNDI ACCESS + else if (dbAccessMethod.equalsIgnoreCase(VALUE_JNDI)){ + // Get the datasource JDNI name: + String dsName = getProperty(tapConfig, KEY_DATASOURCE_JNDI_NAME); + if (dsName == null) + throw new TAPException("The property \"" + KEY_DATASOURCE_JNDI_NAME + "\" is missing! Since the choosen database access method is \"" + VALUE_JNDI + "\", this property is required."); + try{ + // Load the JNDI context: + InitialContext cxt = new InitialContext(); + // Look for the specified datasource: + datasource = (DataSource)cxt.lookup(dsName); + if (datasource == null) + throw new TAPException("No datasource found with the JNDI name \"" + dsName + "\"!"); + // Set the other DB connection parameters: + this.driverPath = null; + this.dbUrl = null; + this.dbUser = null; + this.dbPassword = null; + }catch(NamingException ne){ + throw new TAPException("No datasource found with the JNDI name \"" + dsName + "\"!"); } } + // Case d: unsupported value + else + throw new TAPException("Unsupported value for the property " + KEY_DATABASE_ACCESS + ": \"" + dbAccessMethod + "\"! Allowed values: \"" + VALUE_JNDI + "\" or \"" + VALUE_JDBC + "\"."); - /* 1. Set the ADQLTranslator to use in function of the sql_translator property */ + /* 2. Set the ADQLTranslator to use in function of the sql_translator property */ String sqlTranslator = getProperty(tapConfig, KEY_SQL_TRANSLATOR); - // case a.) no translator specified + // case a: no translator specified if (sqlTranslator == null) throw new TAPException("The property \"" + KEY_SQL_TRANSLATOR + "\" is missing! ADQL queries can not be translated without it. Allowed values: \"" + VALUE_POSTGRESQL + "\", \"" + VALUE_PGSPHERE + "\" or a class path of a class implementing SQLTranslator."); - // case b.) PostgreSQL translator + // case b: PostgreSQL translator else if (sqlTranslator.equalsIgnoreCase(VALUE_POSTGRESQL)) translator = PostgreSQLTranslator.class; - // case c.) PgSphere translator + // case c: PgSphere translator else if (sqlTranslator.equalsIgnoreCase(VALUE_PGSPHERE)) translator = PgSphereTranslator.class; - // case d.) a client defined ADQLTranslator (with the provided class path) + // case d: a client defined ADQLTranslator (with the provided class path) else if (TAPConfiguration.isClassPath(sqlTranslator)) translator = TAPConfiguration.fetchClass(sqlTranslator, KEY_SQL_TRANSLATOR, JDBCTranslator.class); - // case e.) unsupported value + // case e: unsupported value else - throw new TAPException("Unsupported value for the property sql_translator: \"" + sqlTranslator + "\" !"); + throw new TAPException("Unsupported value for the property " + KEY_SQL_TRANSLATOR + ": \"" + sqlTranslator + "\" !"); - /* 2. Test the construction of the ADQLTranslator */ + /* 3. Test the construction of the ADQLTranslator */ createADQLTranslator(); - /* 3. Store the DB connection parameters */ - this.driverPath = jdbcDriver; - this.dbUrl = dbUrl; - this.dbUser = getProperty(tapConfig, KEY_DB_USERNAME);; - this.dbPassword = getProperty(tapConfig, KEY_DB_PASSWORD); - - /* 4. Test the DB connection */ + /* 4. Test the DB connection (note: a translator is needed to create a connection) */ DBConnection dbConn = getConnection("0"); freeConnection(dbConn); /* 5. Set the UWS Backup Parameter */ - // BACKUP FREQUENCY: + // Set the backup frequency: String propValue = getProperty(tapConfig, KEY_BACKUP_FREQUENCY); boolean isTime = false; // determine whether the value is a time period ; if yes, set the frequency: @@ -123,7 +169,7 @@ public final class DefaultTAPFactory extends AbstractTAPFactory { else backupFrequency = DEFAULT_BACKUP_FREQUENCY; } - // BACKUP BY USER: + // Specify whether the backup must be organized by user or not: propValue = getProperty(tapConfig, KEY_BACKUP_BY_USER); backupByUser = (propValue == null) ? DEFAULT_BACKUP_BY_USER : Boolean.parseBoolean(propValue); } @@ -133,7 +179,7 @@ public final class DefaultTAPFactory extends AbstractTAPFactory { * specified by the property sql_translator). If the instance can not be build, * whatever is the reason, a TAPException MUST be thrown. * - * Note: This function is called at the initialization of {@link DefaultTAPFactory} + * Note: This function is called at the initialization of {@link ConfigurableTAPFactory} * in order to check that a translator can be created. */ protected JDBCTranslator createADQLTranslator() throws TAPException{ @@ -143,7 +189,7 @@ public final class DefaultTAPFactory extends AbstractTAPFactory { if (ex instanceof TAPException) throw (TAPException)ex; else - throw new TAPException("Impossible to create a JDBCTranslator instance with the empty constructor of \"" + translator.getName() + "\" (see the property sql_translator) for the following reason: " + ex.getMessage()); + throw new TAPException("Impossible to create a JDBCTranslator instance with the empty constructor of \"" + translator.getName() + "\" (see the property " + KEY_SQL_TRANSLATOR + ") for the following reason: " + ex.getMessage()); } } @@ -156,7 +202,14 @@ public final class DefaultTAPFactory extends AbstractTAPFactory { */ @Override public DBConnection getConnection(String jobID) throws TAPException{ - return new JDBCConnection(driverPath, dbUrl, dbUser, dbPassword, createADQLTranslator(), jobID, this.service.getLogger()); + if (datasource != null){ + try{ + return new JDBCConnection(datasource.getConnection(), createADQLTranslator(), jobID, this.service.getLogger()); + }catch(SQLException se){ + throw new TAPException("Impossible to establish a connection to the database using the set up datasource!", se); + } + }else + return new JDBCConnection(driverPath, dbUrl, dbUser, dbPassword, createADQLTranslator(), jobID, this.service.getLogger()); } @Override @@ -168,11 +221,6 @@ public final class DefaultTAPFactory extends AbstractTAPFactory { } } - @Override - public int countFreeConnections(){ - return 2; // 1 for /sync + 1 for /async - } - @Override public void destroy(){ // Unregister the JDBC driver: diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java index aa60398382ca60282f30baa61913f4fedc2cd834..826cc9f0cd7f1f9da2a81633a8073a88b3a36e5a 100644 --- a/src/tap/config/TAPConfiguration.java +++ b/src/tap/config/TAPConfiguration.java @@ -42,6 +42,13 @@ public final class TAPConfiguration { public final static int DEFAULT_EXECUTION_DURATION = 0; /* DATABASE KEYS */ + public final static String KEY_DATABASE_ACCESS = "database_access"; + public final static String VALUE_JDBC = "jdbc"; + public final static String VALUE_JNDI = "jndi"; + public final static String KEY_SQL_TRANSLATOR = "sql_translator"; + public final static String VALUE_POSTGRESQL = "postgres"; + public final static String VALUE_PGSPHERE = "pgsphere"; + public final static String KEY_DATASOURCE_JNDI_NAME = "datasource_jndi_name"; public final static String KEY_JDBC_DRIVER = "jdbc_driver"; public final static HashMap<String,String> VALUE_JDBC_DRIVERS = new HashMap<String,String>(4); static{ @@ -50,9 +57,6 @@ public final class TAPConfiguration { VALUE_JDBC_DRIVERS.put("mysql", "com.mysql.jdbc.Driver"); VALUE_JDBC_DRIVERS.put("sqlite", "org.sqlite.JDBC"); } - public final static String KEY_SQL_TRANSLATOR = "sql_translator"; - public final static String VALUE_POSTGRESQL = "postgres"; - public final static String VALUE_PGSPHERE = "pgsphere"; public final static String KEY_JDBC_URL = "jdbc_url"; public final static String KEY_DB_USERNAME = "db_username"; public final static String KEY_DB_PASSWORD = "db_password"; diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html index 173dc066d2669fee902bf04ae63e147cef47f5f4..e1febf196de74b24b9d5a37d7e78c2c610c40ec5 100644 --- a/src/tap/config/tap_configuration_file.html +++ b/src/tap/config/tap_configuration_file.html @@ -111,6 +111,44 @@ </tr> <tr><td colspan="5">Database</td></tr> + <tr class="mandatory"> + <td class="done">database_access</td> + <td>M</td> + <td>text</td> + <td> + <p>Method to use in order to create database connections.</p> + <p>Only two values are supported:</p> + <ul> + <li><code>jndi</code>: database connections will be supplied by a Datasource whose the JNDI name must be given. This method may propose connection pooling in function of the datasource configuration.</li> + <li><code>jdbc</code>: the library will create itself connections when they will be needed thanks to the below JDBC parameters. This method does not propose any connection pooling.</li> + </ul> + </td> + <td><ul><li>jdbc</li><li>jndi</li></ul></td> + </tr> + <tr class="mandatory"> + <td class="done">sql_translator</td> + <td>M</td> + <td>text</td> + <td> + <p>The translator to use in order to translate ADQL to a SQL compatible with the used DBMS and its spatial extension.</p> + <p>The TAP library supports only Postgresql (without spatial extension) and PgSphere for the moment. But you can provide your own SQL translator + (even if it does not have spatial features), by providing a path to a class (within brackets: {...}) that implements ADQLTranslator and which have at least an empty constructor.</p> + </td> + <td><ul><li>postgres</li><li>pgsphere</li><li>{apackage.MyADQLTranslator}</li></ul></td> + </tr> + + <tr><td colspan="5">⤷ JNDI datasource <i>(only if database_access=jndi)</i></td></tr> + <tr class="mandatory"> + <td class="done">datasource_jndi_name</td> + <td>M</td> + <td>text</td> + <td> + <p>JNDI name of the datasource. It should be defined in the web application (e.g. in the META-INF/context.xml file in tomcat).</p> + </td> + <td><ul><li>jdbc/postgres</li><li>jdbc/mydatasource</li><li>mydatasource</li><li>...</li></ul></td> + </tr> + + <tr><td colspan="5">⤷ JDBC parameters <i>(only if database_access=jdbc)</i></td></tr> <tr> <td class="done">jdbc_driver</td> <td></td> @@ -160,31 +198,8 @@ </td> <td></td> </tr> - <tr class="mandatory"> - <td class="done">sql_translator</td> - <td>M</td> - <td>text</td> - <td> - <p>The translator to use in order to translate ADQL to a SQL compatible with the used DBMS and its spatial extension.</p> - <p>The TAP library supports only Postgresql (without spatial extension) and PgSphere for the moment. But you can provide your own SQL translator - (even if it does not have spatial features), by providing a path to a class (within brackets: {...}) that implements ADQLTranslator and which have at least an empty constructor.</p> - </td> - <td><ul><li>postgres</li><li>pgsphere</li><li>{apackage.MyADQLTranslator}</li></ul></td> - </tr> <tr><td colspan="5">Metadata</td></tr> - <!-- tr class="mandatory"> - <td class="later">db_tables</td> - <td>M</td> - <td>text</td> - <td> - <p>List all tables that must be accessed thanks to this TAP Service.</p> - <p>Table names must be separated by a comma. A table name may explicitly specify the schema (if not, the table will be considered as part of the "public" schema).</p> - <p>For each table, you can restrict the list of columns that you to expose via the TAP Service. - This list is not mandatory, but if provided it must be within parenthesis and comma separated.</p> - </td> - <td>schema1.*, schema2.table1, table2, table3(col1, col2, col4, ...)</td> - </tr --> <tr class="mandatory"> <td class="done">metadata</td> <td>M</td> diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties index 09a2a82ac88a41921c3c1d8601c25f73cd71e160..affacdb556851e16a84e5d93224dbb899bdf094d 100644 --- a/src/tap/config/tap_full.properties +++ b/src/tap/config/tap_full.properties @@ -2,10 +2,9 @@ # FULL TAP CONFIGURATION FILE # # # # TAP Version: 2.0 # -# Date: 09 Feb. 2015 # +# Date: 12 Feb. 2015 # # Author: Gregory Mantelet (ARI) # # # -# See the TAP documentation for more details: ...TODO... # ########################################################## ########### @@ -32,19 +31,55 @@ tap_description = My TAP Service is so amazing! You should use it with your favo # DATABASE # ############ +# [MANDATORY] +# Method to use in order to create database connections. +# +# Only two values are supported: +# * jndi: database connections will be supplied by a Datasource whose the JNDI name must be given. This method may propose connection pooling in function of the datasource configuration. +# * jdbc: the library will create itself connections when they will be needed thanks to the below JDBC parameters. This method does not propose any connection pooling. +# +# Allowed values: jndi, jdbc. +database_access = + +# [MANDATORY] +# The translator to use in order to translate ADQL to a SQL compatible with the used DBMS and its spatial extension. +# +# The TAP library supports only Postgresql (without spatial extension) and PgSphere for the moment. But you can provide your own SQL translator +# (even if it does not have spatial features), by providing a path to a class (within brackets: {...}) that implements ADQLTranslator (for instance: {apackage.MyADQLTranslator}) +# and which have at least an empty constructor. +# +# Allowed values: postgres, pgsphere, a class path +sql_translator = postgres + +############################# +# IF DATABASE ACCESS = JNDI # +############################# + +# [MANDATORY] +# JNDI name of the datasource pointing toward the database to use. +# It should be defined in the web application (e.g. in the META-INF/context.xml file in tomcat). +datasource_jndi_name = + +############################# +# IF DATABASE ACCESS = JDBC # +############################# + +# [MANDATORY] +# JDBC driver URL pointing toward the database to use. +# +# Note: The username, password or other parameters may be included in it, but in this case, the corresponding properties should leave empty or not provided at all. +jdbc_url = + # [OPTIONAL] -# JDBC driver path. By default, it is guessed in function of the database name provided in the jdbc_url property. It MUST be provided if another DBMS is used or if the JDBC driver path does not match the following ones: +# JDBC driver path. +# +# By default, it is guessed in function of the database name provided in the jdbc_url property. It MUST be provided if another DBMS is used or if the JDBC driver path does not match the following ones: # * Oracle : oracle.jdbc.OracleDriver # * PostgreSQL: org.postgresql.Driver # * MySQL : com.mysql.jdbc.Driver # * SQLite : org.sqlite.JDBC #jdbc_driver = -# [MANDATORY] -# It must be a JDBC driver URL. -# Note: The username, password or other parameters may be included in it, but in this case, the corresponding properties should leave empty or not provided at all. -jdbc_url = - # [MANDATORY] # Mandatory if the username is not already provided in jdbc_url # Username used to access to the database. @@ -53,26 +88,23 @@ db_username = # [MANDATORY] # Mandatory if the password is not already provided in jdbc_url # Password used by db_username to access to the database. +# # Note: No password encryption can be done in this configuration file for the moment. db_password = -# [MANDATORY] -# The translator to use in order to translate ADQL to a SQL compatible with the used DBMS and its spatial extension. -# The TAP library supports only Postgresql (without spatial extension) and PgSphere for the moment. But you can provide your own SQL translator -# (even if it does not have spatial features), by providing a path to a class (within brackets: {...}) that implements ADQLTranslator (for instance: {apackage.MyADQLTranslator}) -# and which have at least an empty constructor. -# Allowed values: postgres, pgsphere, a class path -sql_translator = postgres - ############ # METADATA # ############ # [MANDATORY] -# Metadata fetching method. The value of this key defines the way the library will get the list of all schemas, tables and columns to publish and all their metadata (e.g. utype, description, type, ...) +# Metadata fetching method. +# +# The value of this key defines the way the library will get the list of all schemas, tables and columns to publish and all their metadata (e.g. utype, description, type, ...). +# # In its current state, the library proposes two methods: # 1/ Parse a TableSet XML document and load its content into the database schema TAP_SCHEMA (note: this schema is first erased and rebuilt by the library). -# 2/ Get all metadata from the database schema TAP_SCHEMA. +# 2/ Get all metadata from the database schema TAP_SCHEMA. +# # Allowed values: xml, db. metadata = @@ -87,11 +119,11 @@ metadata_file = # [MANDATORY] # Type of the file manager. -# +# # Accepted values are: local (to manage files on the local system). You can also add another way to manage files by providing # the path (within brackets: {...}) to a class implementing TAPFileManager and having at least one constructor with only a # java.util.Properties parameter. -# +# # Allowed values: local, a class path. file_manager = local @@ -100,28 +132,45 @@ file_manager = local file_root_path = # [OPTIONAL] -# Tells whether a directory should be created for each user. If yes, the user directory will be named with the user ID. In this directory, -# there will be error files, job results and it may be the backup file of the user. +# Tells whether a directory should be created for each user. +# +# If yes, the user directory will be named with the user ID. In this directory, there will be error files, job results and it may be the backup file of the user. +# # Allowed values: true (default), false. directory_per_user = true # [OPTIONAL] -# Tells whether user directories must be grouped. If yes, directories are grouped by the first letter found in the user ID. +# Tells whether user directories must be grouped. +# +# If yes, directories are grouped by the first letter found in the user ID. +# # Allowed values: true (default), false. group_user_dir = true # [OPTIONAL] -# The default period (in seconds) to keep query results. The prefix "default" means here that this value is put by default by the TAP Service if the client does not provide a value for it. -# The default period MUST be less or equals to the maximum retention period. If this rule is not respected, the default retention period is set immediately to the maximum retention period. +# The default period (in seconds) to keep query results. +# +# The prefix "default" means here that this value is put by default by the TAP Service if the client does not provide a value for it. +# +# The default period MUST be less or equals to the maximum retention period. If this rule is not respected, the default retention period is set +# immediately to the maximum retention period. +# # A negative or null value means there is no restriction over the default retention period: job results will be kept forever. Float values are not allowed. -# Default value: 0 (results kept forever). +# +# Default: 0 (results kept forever). default_retention_period = 0 # [OPTIONAL] -# The maximum period (in seconds) to keep query results. The prefix "max" means here that the client can not set a retention period greater than this one. -# The maximum period MUST be greater or equals to the default retention period. If this rule is not respected, the default retention period is set immediately to the maximum retention period. +# The maximum period (in seconds) to keep query results. +# +# The prefix "max" means here that the client can not set a retention period greater than this one. +# +# The maximum period MUST be greater or equals to the default retention period. If this rule is not respected, the default retention period is set +# immediately to the maximum retention period. +# # A negative or null value means there is no restriction over the maximum retention period: the job results will be kept forever. Float values are not allowed. -# Default value: 0 (results kept forever). +# +# Default: 0 (results kept forever). max_retention_period = 0 ############## @@ -130,12 +179,17 @@ max_retention_period = 0 # [OPTIONAL] # Frequency at which the UWS service (that's to say, all its users and jobs) must be backuped. -# Allowed values are: never (no backup will never be done ; default), user_action (each time a user does a writing action, like creating or execution a job), a time (must be positive and not null) in milliseconds. +# +# Allowed values: never (no backup will never be done ; default), user_action (each time a user does a writing action, like creating or execution a job), +# a time (must be positive and not null) in milliseconds. backup_frequency = never # [OPTIONAL] -# Tells whether the backup must be one file for every user, or one file for each user. This second option should be chosen if your TAP Service is organizing its files by user directories ; see the property directory_per_user. -# Allowed values are: user (one backup file for each user ; default), whole (one file for all users ; may generates a big file). +# Tells whether the backup must be one file for every user, or one file for each user. +# +# This second option should be chosen if your TAP Service is organizing its files by user directories ; see the property directory_per_user. +# +# Allowed values: user (one backup file for each user ; default), whole (one file for all users ; may generates a big file). backup_mode = user ##################### @@ -144,8 +198,10 @@ backup_mode = user # [OPTIONAL] # Maximum number of asynchronous jobs that can run simultaneously. +# # A negative or null value means there is no restriction over the number of running asynchronous jobs. -# By default, there is no restriction: max_async_jobs=0. +# +# Default: there is no restriction => max_async_jobs=0. max_async_jobs = 0 ################### @@ -153,17 +209,29 @@ max_async_jobs = 0 ################### # [OPTIONAL] -# Default time (in milliseconds) for query execution. The prefix "default" means here that the execution duration will be this one if the client does not set one. -# The default duration MUST be less or equals to the maximum execution duration. If this rule is not respected, the default execution duration is set immediately to the maximum execution duration. +# Default time (in milliseconds) for query execution. +# +# The prefix "default" means here that the execution duration will be this one if the client does not set one. +# +# The default duration MUST be less or equals to the maximum execution duration. If this rule is not respected, the default execution duration is set +# immediately to the maximum execution duration. +# # A negative or null value means there is no restriction over the default execution duration: the execution could never end. Float values are not allowed. -# By default, there is no restriction: default_execution_duration=0. +# +# Default: there is no restriction => default_execution_duration=0. default_execution_duration = 0 # [OPTIONAL] -# Maximum time (in milliseconds) for query execution. The prefix "max" means here that the client can not set a time greater than this one. -# The maximum duration MUST be greater or equals to the default execution duration. If this rule is not respected, the default execution duration is set immediately to the maximum execution duration. +# Maximum time (in milliseconds) for query execution. +# +# The prefix "max" means here that the client can not set a time greater than this one. +# +# The maximum duration MUST be greater or equals to the default execution duration. If this rule is not respected, the default execution duration is set +# immediately to the maximum execution duration. +# # A negative or null value means there is no restriction over the maximum execution duration: the execution could never end. Float values are not allowed. -# By default, there is no restriction: max_execution_duration=0. +# +# Default: there is no restriction => max_execution_duration=0. max_execution_duration = 0 ########## @@ -172,23 +240,35 @@ max_execution_duration = 0 # [OPTIONAL] # Comma separated list of output formats for query results, in addition to the VOTable. -# Allowed values are: json, csv, tsv, sv(<separator>), or a path (within brackets: {...}) to a class implementing OutputFormat<ResultSet> and having at least one constructor with only a tap.ServiceConnection<ResultSet> parameter. +# +# Allowed values: json, csv, tsv, sv(<separator>), or a path (within brackets: {...}) to a class implementing OutputFormat<ResultSet> +# and having at least one constructor with only a tap.ServiceConnection<ResultSet> parameter. output_add_formats = # [OPTIONAL] -# Default limit for the result output. The prefix "default" means here that this value will be set if the client does not provide one. +# Default limit for the result output. +# +# The prefix "default" means here that this value will be set if the client does not provide one. +# # This limit can be expressed in only one unit: rows. +# # A negative or null value means there is no restriction over this limit. Float values are not allowed. +# # Obviously this limit MUST be less or equal than output_max_limit. -# By default, there is no restriction: output_default_limit=0 +# +# Default: there is no restriction: output_default_limit=0 output_default_limit = 0 # [OPTIONAL] # Maximum limit for the result output. The prefix "max" means here that the client can not set a limit greater than this one. +# # This limit can be expressed in only one unit: rows. +# # A negative or null value means there is no restriction over this limit. Float values are not allowed. +# # Obviously this limit MUST be greater or equal than output_default_limit. -# By default, there is no restriction: output_max_limit=0 +# +# Default: there is no restriction => output_max_limit=0 output_max_limit = 0 ########## @@ -196,33 +276,57 @@ output_max_limit = 0 ########## # [OPTIONAL] -# Tells whether the Upload must be enabled. If enabled, files can be uploaded in the file_root_path, the corresponding tables can be added inside the UPLOAD_SCHEMA +# Tells whether the Upload must be enabled. +# +# If enabled, files can be uploaded in the file_root_path, the corresponding tables can be added inside the UPLOAD_SCHEMA # of the database, queried and then deleted. +# # Allowed values: true (default), false. upload_enabled = false # [OPTIONAL] -# Default limit for the number of uploaded records that can be inserted inside the database. The prefix "default" means here that this value will be set if the client does not provide one. -# This limit can be expressed with 2 types: rows or bytes. For rows, you just have to suffix the value by a "r" (upper- or lower-case), with nothing (by default, nothing will mean "rows"). For bytes, you have to suffix the numeric value by "b", "kb", "Mb" or "Gb". Here, unit is case sensitive (except for the last character: "b"). No other storage unit is allowed. +# Default limit for the number of uploaded records that can be inserted inside the database. +# +# The prefix "default" means here that this value will be set if the client does not provide one. +# +# This limit can be expressed with 2 types: rows or bytes. For rows, you just have to suffix the value by a "r" (upper- or lower-case), +# with nothing (by default, nothing will mean "rows"). For bytes, you have to suffix the numeric value by "b", "kb", "Mb" or "Gb". Here, +# unit is case sensitive (except for the last character: "b"). No other storage unit is allowed. +# # A negative or null value means there is no restriction over this limit. Float values are not allowed. +# # Obviously this limit MUST be less or equal than upload_max_db_limit. -# By default, there is no restriction: upload_default_db_limit=0 +# +# Default: there is no restriction: upload_default_db_limit=0 upload_default_db_limit = 0 # [OPTIONAL] -# Maximum limit for the number of uploaded records that can be inserted inside the database. The prefix "max" means here that the client can not set a limit greater than this one. -# This limit can be expressed with 2 types: rows or bytes. For rows, you just have to suffix the value by a "r" (upper- or lower-case), with nothing (by default, nothing will mean "rows"). For bytes, you have to suffix the numeric value by "b", "kb", "Mb" or "Gb". Here, unit is case sensitive (except for the last character: "b"). No other storage unit is allowed. +# Maximum limit for the number of uploaded records that can be inserted inside the database. +# +# The prefix "max" means here that the client can not set a limit greater than this one. +# +# This limit can be expressed with 2 types: rows or bytes. For rows, you just have to suffix the value by a "r" (upper- or lower-case), +# with nothing (by default, nothing will mean "rows"). For bytes, you have to suffix the numeric value by "b", "kb", "Mb" or "Gb". Here, +# unit is case sensitive (except for the last character: "b"). No other storage unit is allowed. +# # A negative or null value means there is no restriction over this limit. Float values are not allowed. +# # Obviously this limit MUST be greater or equal than upload_default_db_limit. -# By default, there is no restriction: upload_max_db_limit=0 +# +# Default: there is no restriction: upload_max_db_limit=0 upload_max_db_limit = 0 # [OPTIONAL] # Maximum allowed size for the uploaded file. -# This limit MUST be expressed in bytes. Thus, you have to suffix the numeric value by "b", "kb", "Mb" or "Gb". Here, unit is case sensitive (except for the last character: "b"). No other storage unit is allowed. +# +# This limit MUST be expressed in bytes. Thus, you have to suffix the numeric value by "b", "kb", "Mb" or "Gb". Here, unit is case sensitive +# (except for the last character: "b"). No other storage unit is allowed. +# # A negative or null value means there is no restriction over this limit. Float values are not allowed. +# # In function of the chosen upload_max_db_limit type, upload_max_file_size should be greater in order to figure out the metadata part. -# By default, there is no restriction: upload_max_file_size=0 +# +# Default: there is no restriction => upload_max_file_size=0 upload_max_file_size = 0 ####################### @@ -230,8 +334,43 @@ upload_max_file_size = 0 ####################### # [OPTIONAL] -# Class to use in order to identify a user of the TAP service. The same instance of this class will be used for every request sent to the service. +# Class to use in order to identify a user of the TAP service. +# +# The same instance of this class will be used for every request sent to the service. +# # The value of this property MUST be a class path (with brackets: {...}) toward a class implementing the interface uws.service.UserIdentifier. # This class MUST have one of its constructors with no parameter. -# By default, no identification is performed ; all users are then anonymous and their jobs can be seen by everybody. +# +# Default: no identification is performed => all users are then anonymous and their jobs can be seen by everybody. user_identifier = + +############## +# GEOMETRIES # +############## + +# [OPTIONAL] +# Comma-separated list of all allowed geometries. +# +# Each item of the list must be the name (whatever is the case) of an ADQL geometrical function (e.g. INTERSECTS, COORDSYS, POINT) to allow. +# If the list is empty (no item), all functions are allowed. And if the special value NONE is given, no ADQL function will be allowed. +# +# Default: all ADQL geometrical functions are allowed. +geometries = + +################################# +# USER DEFINED FUNCTIONS (UDFs) # +################################# + +# [OPTIONAL] +# Comma-separated list of all allowed UDFs (User Defined Functions). +# +# Each item of the list must have the following syntax: [fct_signature] or [fct_signature, classpath]. fct_function is the function signature. +# Its syntax is the same as in TAPRegExt. classpath is the path of a class extending UserDefinedFunction. An instance of this class will replace +# any reference of a UDF written in an ADQL function with the associated signature. A class path must be specified if the function to represent +# has a signature (and more particularly a name) different in ADQL and in SQL. +# +# If the list is empty (no item), all unknown functions are forbidden. And if the special value ANY is given, any unknown function is allowed ; +# consequently the unknown ADQL functions will be translated into SQL as they are in ADQL. +# +# Default: no unknown function is allowed. +udfs = diff --git a/src/tap/config/tap_min.properties b/src/tap/config/tap_min.properties index 62dffb56b3d6174800942bb88a00d799b2ea10e1..8fe1e9a8d2bca0c145459a47d3a8b715a9f35981 100644 --- a/src/tap/config/tap_min.properties +++ b/src/tap/config/tap_min.properties @@ -2,51 +2,82 @@ # MINIMUM TAP CONFIGURATION FILE # # # # TAP Version: 2.0 # -# Date: 09 Feb. 2015 # +# Date: 12 Feb. 2015 # # Author: Gregory Mantelet (ARI) # # # -# See the TAP documentation for more details: ...TODO... # ########################################################## ############ # DATABASE # ############ -# JDBC driver path. By default, it is guessed in function of the database name provided in the jdbc_url property. It MUST be provided if another DBMS is used or if the JDBC driver path does not match the following ones: -# * Oracle : oracle.jdbc.OracleDriver -# * PostgreSQL: org.postgresql.Driver -# * MySQL : com.mysql.jdbc.Driver -# * SQLite : org.sqlite.JDBC -#jdbc_driver = +# Method to use in order to create database connections. +# +# Only two values are supported: +# * jndi: database connections will be supplied by a Datasource whose the JNDI name must be given. This method may propose connection pooling in function of the datasource configuration. +# * jdbc: the library will create itself connections when they will be needed thanks to the below JDBC parameters. This method does not propose any connection pooling. +# +# Allowed values: jndi, jdbc. +database_access = + +# The translator to use in order to translate ADQL to a SQL compatible with the used DBMS and its spatial extension. +# +# The TAP library supports only Postgresql (without spatial extension) and PgSphere for the moment. But you can provide your own SQL translator +# (even if it does not have spatial features), by providing a path to a class (within brackets: {...}) that implements ADQLTranslator (for instance: {apackage.MyADQLTranslator}) +# and which have at least an empty constructor. +# +# Allowed values: postgres, pgsphere, a class path +sql_translator = postgres + +############################# +# IF DATABASE ACCESS = JNDI # +############################# + +# JNDI name of the datasource. +# +# It should be defined in the web application (e.g. in the META-INF/context.xml file in tomcat). +datasource_jndi_name = + +############################# +# IF DATABASE ACCESS = JDBC # +############################# # It must be a JDBC driver URL. +# # Note: The username, password or other parameters may be included in it, but in this case, the corresponding properties should leave empty or not provided at all. jdbc_url = +# JDBC driver path. +# +# By default, it is guessed in function of the database name provided in the jdbc_url property. It MUST be provided if another DBMS is used or if the JDBC driver path does not match the following ones: +# * Oracle : oracle.jdbc.OracleDriver +# * PostgreSQL: org.postgresql.Driver +# * MySQL : com.mysql.jdbc.Driver +# * SQLite : org.sqlite.JDBC +jdbc_driver = + # Mandatory if the username is not already provided in jdbc_url # Username used to access to the database. db_user = # Mandatory if the password is not already provided in jdbc_url # Password used by db_username to access to the database. +# # Note: No password encryption can be done in this configuration file for the moment. -db_password = - -# The translator to use in order to translate ADQL to a SQL compatible with the used DBMS and its spatial extension. -# The TAP library supports only Postgresql (without spatial extension) and PgSphere for the moment. But you can provide your own SQL translator -# (even if it does not have spatial features), by providing a path to a class (within brackets: {...}) that implements ADQLTranslator (for instance: {apackage.MyADQLTranslator}) -# and which have at least an empty constructor. -# Allowed values: postgres, pgsphere, a class path -sql_translator = postgres +db_password = ############ # METADATA # ############ -# Metadata fetching method. The value of this key defines the way the library will get the list of all schemas, tables and columns to publish and all their metadata (e.g. utype, description, type, ...) +# Metadata fetching method. +# +# The value of this key defines the way the library will get the list of all schemas, tables and columns to publish and all their metadata (e.g. utype, description, type, ...). +# # In its current state, the library proposes two methods: # 1/ Parse a TableSet XML document and load its content into the database schema TAP_SCHEMA (note: this schema is first erased and rebuilt by the library). -# 2/ Get all metadata from the database schema TAP_SCHEMA. +# 2/ Get all metadata from the database schema TAP_SCHEMA. +# # Allowed values: xml, db. metadata = diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java index 07210e903a0e3d5ebd27db9bb80f9730af73ac71..077c4e13edd2c3cfe2e55d4df544f5092d3f4e73 100644 --- a/src/tap/db/JDBCConnection.java +++ b/src/tap/db/JDBCConnection.java @@ -331,7 +331,7 @@ public class JDBCConnection implements DBConnection { Connection con = d.connect(url, p); return con; }catch(SQLException se){ - throw new DBException("Impossible to establish a connection to the database \"" + url + "\" !", se); + throw new DBException("Impossible to establish a connection to the database \"" + url + "\"!", se); } } diff --git a/src/tap/log/TAPLog.java b/src/tap/log/TAPLog.java index a332080de1400636015ce657bef61b5ec2cb2e49..777ec1e3e56ba8423d279d65b5980e1e70f4ec5e 100644 --- a/src/tap/log/TAPLog.java +++ b/src/tap/log/TAPLog.java @@ -16,7 +16,7 @@ package tap.log; * 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) */ @@ -30,7 +30,7 @@ import uws.service.log.UWSLog; * Let log any kind of message about a TAP service. * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (09/2014) + * @version 2.0 (02/2015) */ public interface TAPLog extends UWSLog { @@ -39,6 +39,7 @@ public interface TAPLog extends UWSLog { * * <p>List of all events sent by the library (case sensitive):</p> * <ul> + * <li>CONNECTION_LACK</li> * <li>TRANSLATE</li> * <li>EXECUTE</li> * <li>RESULT</li> diff --git a/src/tap/resource/ASync.java b/src/tap/resource/ASync.java index ccd94df703d6420efad34a6121f91e054fc40e92..8cc6784515ebc58447f96eaf4b63ebe5162eff88 100644 --- a/src/tap/resource/ASync.java +++ b/src/tap/resource/ASync.java @@ -29,7 +29,7 @@ import javax.servlet.http.HttpServletResponse; import tap.ServiceConnection; import tap.TAPException; -import tap.TAPFactory; +import tap.TAPJob; import uws.UWSException; import uws.job.JobList; import uws.job.UWSJob; @@ -71,7 +71,7 @@ import uws.service.log.UWSLog.LogLevel; * </ul> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (12/2014) + * @version 2.0 (02/2015) * * @see UWSService */ @@ -107,7 +107,7 @@ public class ASync implements TAPResource { if (uws.getJobList(getName()) == null){ jobList = new JobList(getName()); uws.addJobList(jobList); - jobList.setExecutionManager(new AsyncExecutionManager(service.getFactory(), service.getLogger(), service.getNbMaxAsyncJobs())); + jobList.setExecutionManager(new AsyncExecutionManager(service.getLogger(), service.getNbMaxAsyncJobs())); }else jobList = uws.getJobList(getName()); @@ -200,36 +200,31 @@ public class ASync implements TAPResource { * when no more DB connection is available for the moment. * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (09/2014) + * @version 2.0 (02/2015) * @since 2.0 */ private class AsyncExecutionManager extends AbstractQueuedExecutionManager { - /** A factory of TAP objects. */ - private final TAPFactory factory; - /** The maximum number of running jobs. */ protected int nbMaxRunningJobs = QueuedExecutionManager.NO_QUEUE; /** * Build a queuing execution manager. * - * @param factory Factory of TAP objects. * @param logger Logger to use. * @param maxRunningJobs Maximum number of asynchronous jobs that can run in the same time. */ - public AsyncExecutionManager(final TAPFactory factory, UWSLog logger, int maxRunningJobs){ + public AsyncExecutionManager(UWSLog logger, int maxRunningJobs){ super(logger); - this.factory = factory; nbMaxRunningJobs = (maxRunningJobs <= 0) ? QueuedExecutionManager.NO_QUEUE : maxRunningJobs; } @Override public boolean isReadyForExecution(final UWSJob jobToExecute){ if (!hasQueue()) - return factory.countFreeConnections() >= 1; + return ((TAPJob)jobToExecute).isReadyForExecution(); else - return (runningJobs.size() < nbMaxRunningJobs) && (factory.countFreeConnections() >= 1); + return (runningJobs.size() < nbMaxRunningJobs) && ((TAPJob)jobToExecute).isReadyForExecution(); } } diff --git a/src/tap/resource/Sync.java b/src/tap/resource/Sync.java index b83fc0714b13bf38ad7771ae88254adaa50c1a89..72550b1e1302f349dce2dcc660d75a1a484627e0 100644 --- a/src/tap/resource/Sync.java +++ b/src/tap/resource/Sync.java @@ -106,23 +106,17 @@ public class Sync implements TAPResource { if (!service.isAvailable()) throw new TAPException("Can not execute a query: this TAP service is not available! " + service.getAvailability(), UWSException.SERVICE_UNAVAILABLE); - /* Ensure that at least 1 DB connection is available for asynchronous queries. - * If yes, just execute synchronously the given job: */ - if (service.getFactory().countFreeConnections() > 1){ - try{ - TAPSyncJob syncJob = new TAPSyncJob(service, params); - syncJob.start(response); - return true; - }catch(TAPException te){ - throw te; - }catch(Exception t){ - service.getLogger().logTAP(LogLevel.FATAL, params, "SYNC_INIT", "Unexpected error while executing the given ADQL query!", t); - throw new TAPException("Unexpected error while executing the given ADQL query!"); - } + // Execute synchronously the given job: + try{ + TAPSyncJob syncJob = new TAPSyncJob(service, params); + syncJob.start(response); + return true; + }catch(TAPException te){ + throw te; + }catch(Exception t){ + service.getLogger().logTAP(LogLevel.FATAL, params, "SYNC_INIT", "Unexpected error while executing the given ADQL query!", t); + throw new TAPException("Unexpected error while executing the given ADQL query!"); } - // Otherwise, send an error: - else - 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); } diff --git a/src/tap/resource/TAP.java b/src/tap/resource/TAP.java index 2341dd98bea7bd29dc3c5faacc871421189b33cf..15e789b341ef8d0a1aabdc0a0cfcfc901f4f6855 100644 --- a/src/tap/resource/TAP.java +++ b/src/tap/resource/TAP.java @@ -751,14 +751,14 @@ public class TAP implements VOSIResource { } // ANY OTHER ERROR: else{ - // Log the error: - getLogger().logHttp(LogLevel.ERROR, response, reqID, owner, "HTTP " + response.getStatus() + " - Can not complete the execution of the TAP resource \"" + resourceName + "\"!", t); // Write the error in the response and return the appropriate HTTP status code: errorWriter.writeError(t, response, request, reqID, owner, resourceName); + // Log the error: + getLogger().logHttp(LogLevel.ERROR, response, reqID, owner, "HTTP " + response.getStatus() + " - Can not complete the execution of the TAP resource \"" + resourceName + "\"!", t); } }finally{ - // Notify the queue of the asynchronous jobs that a new connection is available: - if (resourceName.equalsIgnoreCase(Sync.RESOURCE_NAME) && service.getFactory().countFreeConnections() >= 1) + // Notify the queue of the asynchronous jobs that a new connection may be available: + if (resourceName.equalsIgnoreCase(Sync.RESOURCE_NAME)) getASync().freeConnectionAvailable(); } } diff --git a/test/tap/config/AllTests.java b/test/tap/config/AllTests.java index dffc3cf400393a0431b9c6a62de55c8560930eea..159963e3fbc7801273299061297e25b07dbba75e 100644 --- a/test/tap/config/AllTests.java +++ b/test/tap/config/AllTests.java @@ -9,11 +9,12 @@ import org.junit.runners.Suite.SuiteClasses; import tap.parameters.TestMaxRecController; @RunWith(Suite.class) -@SuiteClasses({TestTAPConfiguration.class,TestDefaultServiceConnection.class,TestDefaultTAPFactory.class,TestMaxRecController.class}) +@SuiteClasses({TestTAPConfiguration.class,TestConfigurableServiceConnection.class,TestConfigurableTAPFactory.class,TestMaxRecController.class}) public class AllTests { public final static Properties getValidProperties(){ Properties validProp = new Properties(); + validProp.setProperty("database_access", "jdbc"); validProp.setProperty("jdbc_url", "jdbc:postgresql:gmantele"); validProp.setProperty("jdbc_driver", "org.postgresql.Driver"); validProp.setProperty("db_username", "gmantele"); diff --git a/test/tap/config/TestDefaultServiceConnection.java b/test/tap/config/TestConfigurableServiceConnection.java similarity index 87% rename from test/tap/config/TestDefaultServiceConnection.java rename to test/tap/config/TestConfigurableServiceConnection.java index 21e9692b11243379c328d9c8f0b58582e80e84c3..e4fb1709b7af70c9b64d0230e58dab4fb186d004 100644 --- a/test/tap/config/TestDefaultServiceConnection.java +++ b/test/tap/config/TestConfigurableServiceConnection.java @@ -35,7 +35,7 @@ import java.util.Properties; import javax.servlet.http.HttpServletRequest; -import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import tap.ServiceConnection; @@ -50,26 +50,27 @@ import uws.service.file.LocalUWSFileManager; import adql.db.FunctionDef; import adql.db.TestDBChecker.UDFToto; -public class TestDefaultServiceConnection { +public class TestConfigurableServiceConnection { private final static String XML_FILE = "test/tap/config/tables.xml"; - private Properties validProp, noFmProp, fmClassPathProp, incorrectFmProp, - xmlMetaProp, missingMetaProp, missingMetaFileProp, wrongMetaProp, - wrongMetaFileProp, validFormatsProp, badSVFormat1Prop, - badSVFormat2Prop, unknownFormatProp, maxAsyncProp, - negativeMaxAsyncProp, notIntMaxAsyncProp, defaultOutputLimitProp, - maxOutputLimitProp, bothOutputLimitGoodProp, - bothOutputLimitBadProp, userIdentProp, notClassPathUserIdentProp, - geometriesProp, noneGeomProp, anyGeomProp, noneInsideGeomProp, - unknownGeomProp, anyUdfsProp, noneUdfsProp, udfsProp, - udfsWithClassPathProp, udfsListWithNONEorANYProp, - udfsWithWrongParamLengthProp, udfsWithMissingBracketsProp, - udfsWithMissingDefProp1, udfsWithMissingDefProp2, - emptyUdfItemProp1, emptyUdfItemProp2, udfWithMissingEndBracketProp; - - @Before - public void setUp() throws Exception{ + private static Properties validProp, noFmProp, fmClassPathProp, + incorrectFmProp, xmlMetaProp, missingMetaProp, missingMetaFileProp, + wrongMetaProp, wrongMetaFileProp, validFormatsProp, + badSVFormat1Prop, badSVFormat2Prop, unknownFormatProp, + maxAsyncProp, negativeMaxAsyncProp, notIntMaxAsyncProp, + defaultOutputLimitProp, maxOutputLimitProp, + bothOutputLimitGoodProp, bothOutputLimitBadProp, userIdentProp, + notClassPathUserIdentProp, geometriesProp, noneGeomProp, + anyGeomProp, noneInsideGeomProp, unknownGeomProp, anyUdfsProp, + noneUdfsProp, udfsProp, udfsWithClassPathProp, + udfsListWithNONEorANYProp, udfsWithWrongParamLengthProp, + udfsWithMissingBracketsProp, udfsWithMissingDefProp1, + udfsWithMissingDefProp2, emptyUdfItemProp1, emptyUdfItemProp2, + udfWithMissingEndBracketProp; + + @BeforeClass + public static void setUp() throws Exception{ // LOAD ALL PROPERTIES FILES NEEDED FOR ALL THE TESTS: validProp = AllTests.getValidProperties(); @@ -207,9 +208,9 @@ public class TestDefaultServiceConnection { * - If a SV format is badly expressed (test with "sv" and "sv()"), an exception must be thrown. * - If an unknown output format is provided an exception must be thrown. * - * Note: the good configuration of the TAPFactory built by the DefaultServiceConnection is tested in {@link TestDefaultTAPFactory}. + * Note: the good configuration of the TAPFactory built by the DefaultServiceConnection is tested in {@link TestConfigurableTAPFactory}. * - * @see DefaultServiceConnection#DefaultServiceConnection(Properties) + * @see ConfigurableServiceConnection#DefaultServiceConnection(Properties) */ @Test public void testDefaultServiceConnectionProperties(){ @@ -218,7 +219,7 @@ public class TestDefaultServiceConnection { int nbSchemas = -1, nbTables = -1; try{ // build the ServiceConnection: - ServiceConnection connection = new DefaultServiceConnection(validProp); + ServiceConnection connection = new ConfigurableServiceConnection(validProp); // tests: assertNotNull(connection.getLogger()); @@ -250,7 +251,7 @@ public class TestDefaultServiceConnection { // Valid XML metadata: try{ - ServiceConnection connection = new DefaultServiceConnection(xmlMetaProp); + ServiceConnection connection = new ConfigurableServiceConnection(xmlMetaProp); assertNotNull(connection.getLogger()); assertNotNull(connection.getFileManager()); assertNotNull(connection.getFactory()); @@ -270,7 +271,7 @@ public class TestDefaultServiceConnection { // Missing metadata property: try{ - new DefaultServiceConnection(missingMetaProp); + new ConfigurableServiceConnection(missingMetaProp); fail("This MUST have failed because the property 'metadata' is missing!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -279,7 +280,7 @@ public class TestDefaultServiceConnection { // Missing metadata_file property: try{ - new DefaultServiceConnection(missingMetaFileProp); + new ConfigurableServiceConnection(missingMetaFileProp); fail("This MUST have failed because the property 'metadata_file' is missing!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -288,7 +289,7 @@ public class TestDefaultServiceConnection { // Wrong metadata property: try{ - new DefaultServiceConnection(wrongMetaProp); + new ConfigurableServiceConnection(wrongMetaProp); fail("This MUST have failed because the property 'metadata' has a wrong value!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -297,7 +298,7 @@ public class TestDefaultServiceConnection { // Wrong metadata_file property: try{ - new DefaultServiceConnection(wrongMetaFileProp); + new ConfigurableServiceConnection(wrongMetaFileProp); fail("This MUST have failed because the property 'metadata_file' has a wrong value!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -306,7 +307,7 @@ public class TestDefaultServiceConnection { // No File Manager: try{ - new DefaultServiceConnection(noFmProp); + new ConfigurableServiceConnection(noFmProp); fail("This MUST have failed because no File Manager is specified!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -315,7 +316,7 @@ public class TestDefaultServiceConnection { // File Manager = Class Path: try{ - ServiceConnection connection = new DefaultServiceConnection(fmClassPathProp); + ServiceConnection connection = new ConfigurableServiceConnection(fmClassPathProp); assertNotNull(connection.getLogger()); assertNotNull(connection.getFileManager()); assertNotNull(connection.getFactory()); @@ -335,7 +336,7 @@ public class TestDefaultServiceConnection { // Incorrect File Manager Value: try{ - new DefaultServiceConnection(incorrectFmProp); + new ConfigurableServiceConnection(incorrectFmProp); fail("This MUST have failed because an incorrect File Manager value has been provided!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -344,7 +345,7 @@ public class TestDefaultServiceConnection { // Valid output formats list: try{ - ServiceConnection connection = new DefaultServiceConnection(validFormatsProp); + ServiceConnection connection = new ConfigurableServiceConnection(validFormatsProp); assertNotNull(connection.getOutputFormat(VALUE_JSON)); assertNotNull(connection.getOutputFormat(VALUE_CSV)); assertNotNull(connection.getOutputFormat(VALUE_TSV)); @@ -360,7 +361,7 @@ public class TestDefaultServiceConnection { // Bad SV(...) format 1 = "sv": try{ - new DefaultServiceConnection(badSVFormat1Prop); + new ConfigurableServiceConnection(badSVFormat1Prop); fail("This MUST have failed because an incorrect SV output format value has been provided!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -369,7 +370,7 @@ public class TestDefaultServiceConnection { // Bad SV(...) format 2 = "sv()": try{ - new DefaultServiceConnection(badSVFormat2Prop); + new ConfigurableServiceConnection(badSVFormat2Prop); fail("This MUST have failed because an incorrect SV output format value has been provided!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -378,7 +379,7 @@ public class TestDefaultServiceConnection { // Unknown output format: try{ - new DefaultServiceConnection(unknownFormatProp); + new ConfigurableServiceConnection(unknownFormatProp); fail("This MUST have failed because an incorrect output format value has been provided!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -387,7 +388,7 @@ public class TestDefaultServiceConnection { // Valid value for max_async_jobs: try{ - ServiceConnection connection = new DefaultServiceConnection(maxAsyncProp); + ServiceConnection connection = new ConfigurableServiceConnection(maxAsyncProp); assertEquals(10, connection.getNbMaxAsyncJobs()); }catch(Exception e){ fail("This MUST have succeeded because a valid max_async_jobs is provided! \nCaught exception: " + getPertinentMessage(e)); @@ -395,7 +396,7 @@ public class TestDefaultServiceConnection { // Negative value for max_async_jobs: try{ - ServiceConnection connection = new DefaultServiceConnection(negativeMaxAsyncProp); + ServiceConnection connection = new ConfigurableServiceConnection(negativeMaxAsyncProp); assertEquals(-2, connection.getNbMaxAsyncJobs()); }catch(Exception e){ fail("This MUST have succeeded because a negative max_async_jobs is equivalent to 'no restriction'! \nCaught exception: " + getPertinentMessage(e)); @@ -403,7 +404,7 @@ public class TestDefaultServiceConnection { // A not integer value for max_async_jobs: try{ - new DefaultServiceConnection(notIntMaxAsyncProp); + new ConfigurableServiceConnection(notIntMaxAsyncProp); fail("This MUST have failed because a not integer value has been provided for \"" + KEY_MAX_ASYNC_JOBS + "\"!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -412,7 +413,7 @@ public class TestDefaultServiceConnection { // Test with no output limit specified: try{ - ServiceConnection connection = new DefaultServiceConnection(validProp); + ServiceConnection connection = new ConfigurableServiceConnection(validProp); assertEquals(connection.getOutputLimit()[0], -1); assertEquals(connection.getOutputLimit()[1], -1); assertEquals(connection.getOutputLimitType()[0], LimitUnit.rows); @@ -423,7 +424,7 @@ public class TestDefaultServiceConnection { // Test with only a set default output limit: try{ - ServiceConnection connection = new DefaultServiceConnection(defaultOutputLimitProp); + ServiceConnection connection = new ConfigurableServiceConnection(defaultOutputLimitProp); assertEquals(connection.getOutputLimit()[0], 100); assertEquals(connection.getOutputLimit()[1], -1); assertEquals(connection.getOutputLimitType()[0], LimitUnit.rows); @@ -434,7 +435,7 @@ public class TestDefaultServiceConnection { // Test with only a set maximum output limit: try{ - ServiceConnection connection = new DefaultServiceConnection(maxOutputLimitProp); + ServiceConnection connection = new ConfigurableServiceConnection(maxOutputLimitProp); assertEquals(connection.getOutputLimit()[0], -1); assertEquals(connection.getOutputLimit()[1], 1000); assertEquals(connection.getOutputLimitType()[0], LimitUnit.rows); @@ -445,7 +446,7 @@ public class TestDefaultServiceConnection { // Test with both a default and a maximum output limits where default <= max: try{ - ServiceConnection connection = new DefaultServiceConnection(bothOutputLimitGoodProp); + ServiceConnection connection = new ConfigurableServiceConnection(bothOutputLimitGoodProp); assertEquals(connection.getOutputLimit()[0], 100); assertEquals(connection.getOutputLimit()[1], 1000); assertEquals(connection.getOutputLimitType()[0], LimitUnit.rows); @@ -456,7 +457,7 @@ public class TestDefaultServiceConnection { // Test with both a default and a maximum output limits BUT where default > max: try{ - new DefaultServiceConnection(bothOutputLimitBadProp); + new ConfigurableServiceConnection(bothOutputLimitBadProp); fail("This MUST have failed because the default output limit is greater than the maximum one!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -465,7 +466,7 @@ public class TestDefaultServiceConnection { // Valid user identifier: try{ - ServiceConnection connection = new DefaultServiceConnection(userIdentProp); + ServiceConnection connection = new ConfigurableServiceConnection(userIdentProp); assertNotNull(connection.getUserIdentifier()); assertNotNull(connection.getUserIdentifier().extractUserId(null, null)); assertEquals("everybody", connection.getUserIdentifier().extractUserId(null, null).getID()); @@ -475,7 +476,7 @@ public class TestDefaultServiceConnection { // Not a class path for user_identifier: try{ - new DefaultServiceConnection(notClassPathUserIdentProp); + new ConfigurableServiceConnection(notClassPathUserIdentProp); fail("This MUST have failed because the user_identifier value is not a class path!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -484,7 +485,7 @@ public class TestDefaultServiceConnection { // Valid geometry list: try{ - ServiceConnection connection = new DefaultServiceConnection(geometriesProp); + ServiceConnection connection = new ConfigurableServiceConnection(geometriesProp); assertNotNull(connection.getGeometries()); assertEquals(4, connection.getGeometries().size()); assertEquals("POINT", ((ArrayList<String>)connection.getGeometries()).get(0)); @@ -497,7 +498,7 @@ public class TestDefaultServiceConnection { // "NONE" as geometry list: try{ - ServiceConnection connection = new DefaultServiceConnection(noneGeomProp); + ServiceConnection connection = new ConfigurableServiceConnection(noneGeomProp); assertNotNull(connection.getGeometries()); assertEquals(0, connection.getGeometries().size()); }catch(Exception e){ @@ -506,7 +507,7 @@ public class TestDefaultServiceConnection { // "ANY" as geometry list: try{ - ServiceConnection connection = new DefaultServiceConnection(anyGeomProp); + ServiceConnection connection = new ConfigurableServiceConnection(anyGeomProp); assertNull(connection.getGeometries()); }catch(Exception e){ fail("This MUST have succeeded because the given list of geometries is correct (reduced to only ANY)! \nCaught exception: " + getPertinentMessage(e)); @@ -514,7 +515,7 @@ public class TestDefaultServiceConnection { // "NONE" inside a geometry list: try{ - new DefaultServiceConnection(noneInsideGeomProp); + new ConfigurableServiceConnection(noneInsideGeomProp); fail("This MUST have failed because the given geometry list contains at least 2 items, whose one is NONE!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -523,7 +524,7 @@ public class TestDefaultServiceConnection { // Unknown geometrical function: try{ - new DefaultServiceConnection(unknownGeomProp); + new ConfigurableServiceConnection(unknownGeomProp); fail("This MUST have failed because the given geometry list contains at least 1 unknown ADQL geometrical function!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -532,7 +533,7 @@ public class TestDefaultServiceConnection { // "ANY" as UDFs list: try{ - ServiceConnection connection = new DefaultServiceConnection(anyUdfsProp); + ServiceConnection connection = new ConfigurableServiceConnection(anyUdfsProp); assertNull(connection.getUDFs()); }catch(Exception e){ fail("This MUST have succeeded because the given list of UDFs is correct (reduced to only ANY)! \nCaught exception: " + getPertinentMessage(e)); @@ -540,7 +541,7 @@ public class TestDefaultServiceConnection { // "NONE" as UDFs list: try{ - ServiceConnection connection = new DefaultServiceConnection(noneUdfsProp); + ServiceConnection connection = new ConfigurableServiceConnection(noneUdfsProp); assertNotNull(connection.getUDFs()); assertEquals(0, connection.getUDFs().size()); }catch(Exception e){ @@ -549,7 +550,7 @@ public class TestDefaultServiceConnection { // Valid list of UDFs: try{ - ServiceConnection connection = new DefaultServiceConnection(udfsProp); + ServiceConnection connection = new ConfigurableServiceConnection(udfsProp); assertNotNull(connection.getUDFs()); assertEquals(2, connection.getUDFs().size()); Iterator<FunctionDef> it = connection.getUDFs().iterator(); @@ -561,7 +562,7 @@ public class TestDefaultServiceConnection { // Valid list of UDFs containing one UDF with a classpath: try{ - ServiceConnection connection = new DefaultServiceConnection(udfsWithClassPathProp); + ServiceConnection connection = new ConfigurableServiceConnection(udfsWithClassPathProp); assertNotNull(connection.getUDFs()); assertEquals(1, connection.getUDFs().size()); FunctionDef def = connection.getUDFs().iterator().next(); @@ -573,7 +574,7 @@ public class TestDefaultServiceConnection { // "NONE" inside a UDFs list: try{ - new DefaultServiceConnection(udfsListWithNONEorANYProp); + new ConfigurableServiceConnection(udfsListWithNONEorANYProp); fail("This MUST have failed because the given UDFs list contains at least 2 items, whose one is ANY!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -582,7 +583,7 @@ public class TestDefaultServiceConnection { // UDF with no brackets: try{ - new DefaultServiceConnection(udfsWithMissingBracketsProp); + new ConfigurableServiceConnection(udfsWithMissingBracketsProp); fail("This MUST have failed because one UDFs list item has no brackets!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -591,7 +592,7 @@ public class TestDefaultServiceConnection { // UDFs whose one item have more parts than supported: try{ - new DefaultServiceConnection(udfsWithWrongParamLengthProp); + new ConfigurableServiceConnection(udfsWithWrongParamLengthProp); fail("This MUST have failed because one UDFs list item has too many parameters!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -600,7 +601,7 @@ public class TestDefaultServiceConnection { // UDF with missing definition part (or wrong since there is no comma): try{ - new DefaultServiceConnection(udfsWithMissingDefProp1); + new ConfigurableServiceConnection(udfsWithMissingDefProp1); fail("This MUST have failed because one UDFs list item has a wrong signature part (it has been forgotten)!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -609,7 +610,7 @@ public class TestDefaultServiceConnection { // UDF with missing definition part (or wrong since there is no comma): try{ - new DefaultServiceConnection(udfsWithMissingDefProp2); + new ConfigurableServiceConnection(udfsWithMissingDefProp2); fail("This MUST have failed because one UDFs list item has no signature part!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -618,7 +619,7 @@ public class TestDefaultServiceConnection { // Empty UDF item (without comma): try{ - ServiceConnection connection = new DefaultServiceConnection(emptyUdfItemProp1); + ServiceConnection connection = new ConfigurableServiceConnection(emptyUdfItemProp1); assertNotNull(connection.getUDFs()); assertEquals(0, connection.getUDFs().size()); }catch(Exception e){ @@ -627,7 +628,7 @@ public class TestDefaultServiceConnection { // Empty UDF item (with comma): try{ - ServiceConnection connection = new DefaultServiceConnection(emptyUdfItemProp2); + ServiceConnection connection = new ConfigurableServiceConnection(emptyUdfItemProp2); assertNotNull(connection.getUDFs()); assertEquals(0, connection.getUDFs().size()); }catch(Exception e){ @@ -636,7 +637,7 @@ public class TestDefaultServiceConnection { // UDF item without its closing bracket: try{ - new DefaultServiceConnection(udfWithMissingEndBracketProp); + new ConfigurableServiceConnection(udfWithMissingEndBracketProp); fail("This MUST have failed because one UDFs list item has no closing bracket!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); @@ -653,7 +654,7 @@ public class TestDefaultServiceConnection { * * @author Grégory Mantelet (ARI) * @version 01/2015 - * @see TestDefaultServiceConnection#testDefaultServiceConnectionProperties() + * @see TestConfigurableServiceConnection#testDefaultServiceConnectionProperties() */ public static class FileManagerTest extends LocalUWSFileManager { public FileManagerTest(Properties tapConfig) throws UWSException{ diff --git a/test/tap/config/TestDefaultTAPFactory.java b/test/tap/config/TestConfigurableTAPFactory.java similarity index 52% rename from test/tap/config/TestDefaultTAPFactory.java rename to test/tap/config/TestConfigurableTAPFactory.java index 69d7eeecea9953f80b9d0a0835f6d0375d9e8ebd..f270ad1394c4cebc5e5c9d2937cbcfb163b8357f 100644 --- a/test/tap/config/TestDefaultTAPFactory.java +++ b/test/tap/config/TestConfigurableTAPFactory.java @@ -5,27 +5,39 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static tap.config.TAPConfiguration.KEY_DATABASE_ACCESS; +import static tap.config.TAPConfiguration.KEY_DATASOURCE_JNDI_NAME; import static tap.config.TAPConfiguration.KEY_DB_PASSWORD; import static tap.config.TAPConfiguration.KEY_DB_USERNAME; import static tap.config.TAPConfiguration.KEY_JDBC_DRIVER; import static tap.config.TAPConfiguration.KEY_JDBC_URL; import static tap.config.TAPConfiguration.KEY_SQL_TRANSLATOR; +import static tap.config.TAPConfiguration.VALUE_JDBC; +import static tap.config.TAPConfiguration.VALUE_JNDI; import static tap.config.TAPConfiguration.VALUE_PGSPHERE; import static tap.config.TAPConfiguration.VALUE_POSTGRESQL; import java.io.File; +import java.sql.SQLException; import java.util.Collection; import java.util.Iterator; import java.util.Properties; -import org.junit.Before; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.junit.BeforeClass; import org.junit.Test; +import org.postgresql.ds.PGSimpleDataSource; import org.postgresql.util.PSQLException; import tap.ServiceConnection; import tap.TAPException; import tap.TAPFactory; +import tap.db.DBConnection; import tap.db.DBException; +import tap.db.JDBCConnection; import tap.formatter.OutputFormat; import tap.log.DefaultTAPLog; import tap.log.TAPLog; @@ -36,78 +48,193 @@ import uws.service.file.LocalUWSFileManager; import uws.service.file.UWSFileManager; import adql.db.FunctionDef; -public class TestDefaultTAPFactory { +public class TestConfigurableTAPFactory { - private Properties validProp, noJdbcProp1, noJdbcProp2, badJdbcProp, + private static Properties validJDBCProp, validJNDIProp, + incorrectDBAccessProp, missingDBAccessProp, + missingDatasourceJNDINameProp, wrongDatasourceJNDINameProp, + noJdbcProp1, noJdbcProp2, noJdbcProp3, badJdbcProp, missingTranslatorProp, badTranslatorProp, badDBNameProp, badUsernameProp, badPasswordProp; - private ServiceConnection serviceConnection = null; + private static ServiceConnection serviceConnection = null; + + private static void setJNDIDatasource() throws NamingException{ + // Create an initial JNDI context: + /* note: this requires that the simple-jndi jar is in the classpath. (https://code.google.com/p/osjava/downloads/detail?name=simple-jndi-0.11.4.1.zip&can=2&q=) */ + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.memory.MemoryContextFactory"); + System.setProperty("org.osjava.sj.jndi.shared", "true"); // memory shared between all instances of InitialContext + + // Context initialization: + InitialContext ic = new InitialContext(); - @Before - public void setUp() throws Exception{ + // Creation of a reference on a DataSource: + PGSimpleDataSource datasource = new PGSimpleDataSource(); + datasource.setServerName("localhost"); + datasource.setDatabaseName("gmantele"); + + // Link the datasource with the context: + ic.rebind("jdbc/MyDataSource", datasource); + } + + @BeforeClass + public static void beforeClass() throws Exception{ // BUILD A FAKE SERVICE CONNECTION: serviceConnection = new ServiceConnectionTest(); // LOAD ALL PROPERTIES FILES NEEDED FOR ALL THE TESTS: - validProp = AllTests.getValidProperties(); + validJDBCProp = AllTests.getValidProperties(); + + setJNDIDatasource(); + validJNDIProp = (Properties)validJDBCProp.clone(); + validJNDIProp.setProperty(KEY_DATABASE_ACCESS, "jndi"); + validJNDIProp.setProperty(KEY_DATASOURCE_JNDI_NAME, "jdbc/MyDataSource"); + validJNDIProp.remove(KEY_JDBC_URL); + validJNDIProp.remove(KEY_JDBC_DRIVER); + validJNDIProp.remove(KEY_DB_USERNAME); + validJNDIProp.remove(KEY_DB_PASSWORD); + + incorrectDBAccessProp = (Properties)validJDBCProp.clone(); + incorrectDBAccessProp.setProperty(KEY_DATABASE_ACCESS, "foo"); + + missingDBAccessProp = (Properties)validJDBCProp.clone(); + missingDBAccessProp.remove(KEY_DATABASE_ACCESS); - noJdbcProp1 = (Properties)validProp.clone(); + missingDatasourceJNDINameProp = (Properties)validJNDIProp.clone(); + missingDatasourceJNDINameProp.remove(KEY_DATASOURCE_JNDI_NAME); + + wrongDatasourceJNDINameProp = (Properties)validJNDIProp.clone(); + wrongDatasourceJNDINameProp.setProperty(KEY_DATASOURCE_JNDI_NAME, "foo"); + + noJdbcProp1 = (Properties)validJDBCProp.clone(); noJdbcProp1.remove(KEY_JDBC_DRIVER); noJdbcProp2 = (Properties)noJdbcProp1.clone(); noJdbcProp2.setProperty(KEY_JDBC_URL, "jdbc:foo:gmantele"); - badJdbcProp = (Properties)validProp.clone(); + noJdbcProp3 = (Properties)noJdbcProp1.clone(); + noJdbcProp3.remove(KEY_JDBC_URL); + + badJdbcProp = (Properties)validJDBCProp.clone(); badJdbcProp.setProperty(KEY_JDBC_DRIVER, "foo"); badJdbcProp.setProperty(KEY_JDBC_URL, "jdbc:foo:gmantele"); - missingTranslatorProp = (Properties)validProp.clone(); + missingTranslatorProp = (Properties)validJDBCProp.clone(); missingTranslatorProp.remove(KEY_SQL_TRANSLATOR); - badTranslatorProp = (Properties)validProp.clone(); + badTranslatorProp = (Properties)validJDBCProp.clone(); badTranslatorProp.setProperty(KEY_SQL_TRANSLATOR, "foo"); - badDBNameProp = (Properties)validProp.clone(); + badDBNameProp = (Properties)validJDBCProp.clone(); badDBNameProp.setProperty(KEY_JDBC_URL, "jdbc:postgresql:foo"); - badUsernameProp = (Properties)validProp.clone(); + badUsernameProp = (Properties)validJDBCProp.clone(); badUsernameProp.setProperty(KEY_DB_USERNAME, "foo"); - badPasswordProp = (Properties)validProp.clone(); + badPasswordProp = (Properties)validJDBCProp.clone(); badPasswordProp.setProperty(KEY_DB_PASSWORD, "foo"); } @Test public void testDefaultServiceConnection(){ - // Correct Parameters: + // Correct Parameters (JDBC CASE): + DBConnection connection = null; try{ - TAPFactory factory = new DefaultTAPFactory(serviceConnection, validProp); - assertNotNull(factory.getConnection("0")); + TAPFactory factory = new ConfigurableTAPFactory(serviceConnection, validJDBCProp); + connection = factory.getConnection("0"); + assertNotNull(connection); assertNull(factory.createUWSBackupManager(new UWSService(factory, new LocalUWSFileManager(new File("."))))); }catch(Exception ex){ fail(getPertinentMessage(ex)); + }finally{ + if (connection != null){ + try{ + ((JDBCConnection)connection).getInnerConnection().close(); + connection = null; + }catch(SQLException se){} + } + } + + // Correct Parameters (JNDI CASE): + try{ + TAPFactory factory = new ConfigurableTAPFactory(serviceConnection, validJNDIProp); + connection = factory.getConnection("0"); + assertNotNull(connection); + }catch(Exception ex){ + fail(getPertinentMessage(ex)); + }finally{ + if (connection != null){ + try{ + ((JDBCConnection)connection).getInnerConnection().close(); + connection = null; + }catch(SQLException se){} + } + } + + // Incorrect database access method: + try{ + new ConfigurableServiceConnection(incorrectDBAccessProp); + fail("This MUST have failed because the value of the property '" + KEY_DATABASE_ACCESS + "' is incorrect!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Unsupported value for the property " + KEY_DATABASE_ACCESS + ": \"foo\"! Allowed values: \"" + VALUE_JNDI + "\" or \"" + VALUE_JDBC + "\".", e.getMessage()); + } + + // Missing database access method: + try{ + new ConfigurableServiceConnection(missingDBAccessProp); + fail("This MUST have failed because the property '" + KEY_DATABASE_ACCESS + "' is missing!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("The property \"" + KEY_DATABASE_ACCESS + "\" is missing! It is required to connect to the database. Two possible values: \"" + VALUE_JDBC + "\" and \"" + VALUE_JNDI + "\".", e.getMessage()); + } + + // Missing JNDI name: + try{ + new ConfigurableServiceConnection(missingDatasourceJNDINameProp); + fail("This MUST have failed because the property '" + KEY_DATASOURCE_JNDI_NAME + "' is missing!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("The property \"" + KEY_DATASOURCE_JNDI_NAME + "\" is missing! Since the choosen database access method is \"" + VALUE_JNDI + "\", this property is required.", e.getMessage()); + } + + // Wrong JNDI name: + try{ + new ConfigurableServiceConnection(wrongDatasourceJNDINameProp); + fail("This MUST have failed because the value of the property '" + KEY_DATASOURCE_JNDI_NAME + "' is incorrect!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("No datasource found with the JNDI name \"foo\"!", e.getMessage()); } // No JDBC Driver but the database type is known: try{ - new DefaultTAPFactory(serviceConnection, noJdbcProp1); + new ConfigurableTAPFactory(serviceConnection, noJdbcProp1); }catch(Exception ex){ fail(getPertinentMessage(ex)); } // No JDBC Driver but the database type is UNKNOWN: try{ - new DefaultTAPFactory(serviceConnection, noJdbcProp2); + new ConfigurableTAPFactory(serviceConnection, noJdbcProp2); fail("This MUST have failed because no JDBC Driver has been successfully guessed from the database type!"); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); assertTrue(ex.getMessage().matches("No JDBC driver known for the DBMS \"[^\\\"]*\"!")); } + // Missing JDBC URL: + try{ + new ConfigurableTAPFactory(serviceConnection, noJdbcProp3); + fail("This MUST have failed because the property \"" + KEY_JDBC_URL + "\" is missing!"); + }catch(Exception ex){ + assertEquals(TAPException.class, ex.getClass()); + assertTrue(ex.getMessage().matches("The property \"" + KEY_JDBC_URL + "\" is missing! Since the choosen database access method is \"" + VALUE_JDBC + "\", this property is required.")); + } + // Bad JDBC Driver: try{ - new DefaultTAPFactory(serviceConnection, badJdbcProp); + new ConfigurableTAPFactory(serviceConnection, badJdbcProp); fail("This MUST have failed because the provided JDBC Driver doesn't exist!"); }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); @@ -116,7 +243,7 @@ public class TestDefaultTAPFactory { // Missing Translator: try{ - new DefaultTAPFactory(serviceConnection, missingTranslatorProp); + new ConfigurableTAPFactory(serviceConnection, missingTranslatorProp); fail("This MUST have failed because the provided SQL translator is missing!"); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); @@ -125,7 +252,7 @@ public class TestDefaultTAPFactory { // Bad Translator: try{ - new DefaultTAPFactory(serviceConnection, badTranslatorProp); + new ConfigurableTAPFactory(serviceConnection, badTranslatorProp); fail("This MUST have failed because the provided SQL translator is incorrect!"); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); @@ -134,33 +261,33 @@ public class TestDefaultTAPFactory { // Bad DB Name: try{ - new DefaultTAPFactory(serviceConnection, badDBNameProp); + new ConfigurableTAPFactory(serviceConnection, badDBNameProp); fail("This MUST have failed because the provided database name is incorrect!"); }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); - assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\" !")); + assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\"!")); assertEquals(PSQLException.class, ex.getCause().getClass()); assertTrue(ex.getCause().getMessage().matches("FATAL: password authentication failed for user \"[^\\\"]*\"")); } // Bad DB Username: ABORTED BECAUSE THE BAD USERNAME IS NOT DETECTED FOR THE DB WHICH HAS THE SAME NAME AS THE USERNAME ! try{ - new DefaultTAPFactory(serviceConnection, badUsernameProp); + new ConfigurableTAPFactory(serviceConnection, badUsernameProp); fail("This MUST have failed because the provided database username is incorrect!"); }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); - assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\" !")); + assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\"!")); assertEquals(PSQLException.class, ex.getCause().getClass()); assertTrue(ex.getCause().getMessage().matches("FATAL: password authentication failed for user \"[^\\\"]*\"")); } // Bad DB Password: try{ - new DefaultTAPFactory(serviceConnection, badPasswordProp); + new ConfigurableTAPFactory(serviceConnection, badPasswordProp); //fail("This MUST have failed because the provided database password is incorrect!"); // NOTE: In function of the database configuration, a password may be required or not. So this test is not automatic! }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); - assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\" !")); + assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\"!")); assertEquals(PSQLException.class, ex.getCause().getClass()); assertTrue(ex.getCause().getMessage().matches("FATAL: password authentication failed for user \"[^\\\"]*\"")); } diff --git a/test/tap/config/TestTAPConfiguration.java b/test/tap/config/TestTAPConfiguration.java index 44a20d5faa3da2b5e59c23e188fdc45d5742f9f2..5cf52c268b1c43b0554e73607fef5b1f36400711 100644 --- a/test/tap/config/TestTAPConfiguration.java +++ b/test/tap/config/TestTAPConfiguration.java @@ -29,7 +29,7 @@ public class TestTAPConfiguration { * - null, "", "{}", "an incorrect syntax" => FALSE must be returned * - "{ }", "{ }", "{class.path}", "{ class.path }" => TRUE must be returned * - * @see DefaultServiceConnection#isClassPath(String) + * @see ConfigurableServiceConnection#isClassPath(String) */ @Test public void testIsClassPath(){ @@ -268,7 +268,7 @@ public class TestTAPConfiguration { fail("This test should have failed because an incorrect limit is provided: \"abc100b\"!"); }catch(TAPException te){ assertEquals(te.getClass(), TAPException.class); - assertEquals(te.getMessage(), "Numeric value expected for the property " + propertyName + " for the substring \"abc100\" of the whole value: \"abc100b\"!"); + assertEquals(te.getMessage(), "Integer expected for the property " + propertyName + " for the substring \"abc100\" of the whole value: \"abc100b\"!"); } // Test with a BYTES unit whereas the BYTES unit is forbidden: