diff --git a/src/tap/AbstractTAPFactory.java b/src/tap/AbstractTAPFactory.java index 34e61ef322d4afeef489dd387076221cddd2db0e..f619b035f660d6cd8928285eb6c4430063473247 100644 --- a/src/tap/AbstractTAPFactory.java +++ b/src/tap/AbstractTAPFactory.java @@ -20,13 +20,17 @@ package tap; * Astronomisches Rechen Institut (ARI) */ +import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import tap.db.DBConnection; +import tap.metadata.TAPMetadata; +import tap.metadata.TAPSchema; import tap.metadata.TAPTable; import tap.parameters.TAPParameters; import tap.upload.Uploader; @@ -39,18 +43,181 @@ import uws.service.backup.UWSBackupManager; import adql.db.DBChecker; import adql.parser.ADQLQueryFactory; import adql.parser.QueryChecker; +import adql.query.ADQLQuery; +/** + * Default implementation of most of the {@link TAPFactory} function. + * Only the functions related with the database connection stay abstract. + * + * @author Grégory Mantelet (ARI) + * @version 2.0 (08/2014) + */ public abstract class AbstractTAPFactory extends TAPFactory { + /** + * Build a basic TAPFactory. + * Nothing is done except setting the service connection. + * + * @param service Configuration of the TAP service. <i>MUST NOT be NULL</i> + * + * @throws NullPointerException If the given {@link ServiceConnection} is NULL. + * + * @see {@link TAPFactory#TAPFactory(ServiceConnection)} + */ protected AbstractTAPFactory(ServiceConnection service) throws NullPointerException{ super(service); } + /* *************** */ + /* ADQL MANAGEMENT */ + /* *************** */ + + /** + * <p><i>Note: + * Unless the standard implementation - {@link ADQLExecutor} - does not fit exactly your needs, + * it should not be necessary to extend this class and to extend this function (implemented here by default). + * </i></p> + * + * @see tap.TAPFactory#createADQLExecutor() + */ + @Override + public ADQLExecutor createADQLExecutor() throws TAPException{ + return new ADQLExecutor(service); + } + + /** + * <p><i>Note: + * This function should be extended if you have customized the creation of any + * {@link ADQLQuery} part ; it could be the addition of one or several user defined function + * or the modification of any ADQL function or clause specific to your implementation. + * </i></p> + * + * @see tap.TAPFactory#createQueryFactory() + */ + @Override + public ADQLQueryFactory createQueryFactory() throws TAPException{ + return new ADQLQueryFactory(); + } + + /** + * <p>This implementation gathers all tables published in this TAP service and those uploaded + * by the user. Then it calls {@link #createQueryChecker(Collection)} with this list in order + * to create a query checked. + * </p> + * + * <p><i>Note: + * This function can not be overrided, but {@link #createQueryChecker(Collection)} can be. + * </i></p> + * + * @see tap.TAPFactory#createQueryChecker(tap.metadata.TAPSchema) + */ + @Override + public final QueryChecker createQueryChecker(final TAPSchema uploadSchema) throws TAPException{ + // Get all tables published in this TAP service: + TAPMetadata meta = service.getTAPMetadata(); + + // Build a list in order to gather all these with the uploaded ones: + ArrayList<TAPTable> tables = new ArrayList<TAPTable>(meta.getNbTables()); + + // Add all tables published in TAP: + Iterator<TAPTable> it = meta.getTables(); + while(it.hasNext()) + tables.add(it.next()); + + // Add all tables uploaded by the user: + if (uploadSchema != null){ + for(TAPTable table : uploadSchema) + tables.add(table); + } + + // Finally, create the query checker: + return createQueryChecker(tables); + } + + /** + * <p>Create an object able to check the consistency between the ADQL query and the database. + * That's to say, it checks whether the tables and columns used in the query really exist + * in the database.</p> + * + * <p><i>Note: + * This implementation just create a {@link DBChecker} instance with the list given in parameter. + * </i></p> + * + * @param tables List of all available tables (and indirectly, columns). + * + * @return A new ADQL query checker. + * + * @throws TAPException If any error occurs while creating the query checker. + * + * @see DBChecker + */ + protected QueryChecker createQueryChecker(final Collection<TAPTable> tables) throws TAPException{ + return new DBChecker(tables); + } + + /* ****** */ + /* UPLOAD */ + /* ****** */ + + /** + * <p>This implementation just create an {@link Uploader} instance with the given database connection.</p> + * + * <p><i>Note: + * This function should be overrided if the way user tables are interpreted and then created into the database + * does not fit yours needs. + * </i></p> + * + * @see tap.TAPFactory#createUploader(tap.db.DBConnection) + * + * @see Uploader + */ + @Override + public Uploader createUploader(final DBConnection dbConn) throws TAPException{ + return new Uploader(service, dbConn); + } + + /* ************** */ + /* UWS MANAGEMENT */ + /* ************** */ + + /** + * <p>This implementation just create a {@link UWSService} instance.</p> + * + * <p><i>Note: + * This implementation is largely enough for a TAP service. It is not recommended to override + * this function. + * </i></p> + * + * @see tap.TAPFactory#createUWS() + */ + @Override + public UWSService createUWS() throws TAPException{ + return new UWSService(this, this.service.getFileManager(), this.service.getLogger()); + } + + /** + * <p>This implementation does not provided a backup manager. + * It means that no asynchronous job will be restored and backuped.</p> + * + * <p>You must override this function if you want enable the backup feature.</p> + * + * @see tap.TAPFactory#createUWSBackupManager(uws.service.UWSService) + */ @Override public UWSBackupManager createUWSBackupManager(final UWSService uws) throws TAPException{ return null; } + /** + * <p>This implementation provides a basic {@link TAPJob} instance.</p> + * + * <p> + * If you need to add or modify the behavior of some functions of a {@link TAPJob}, + * you must override this function and return your own extension of {@link TAPJob}. + * </p> + * + * @see tap.TAPFactory#createTAPJob(javax.servlet.http.HttpServletRequest, uws.job.user.JobOwner) + */ @Override protected TAPJob createTAPJob(final HttpServletRequest request, final JobOwner owner) throws UWSException{ try{ @@ -61,6 +228,16 @@ public abstract class AbstractTAPFactory extends TAPFactory { } } + /** + * <p>This implementation provides a basic {@link TAPJob} instance.</p> + * + * <p> + * If you need to add or modify the behavior of some functions of a {@link TAPJob}, + * you must override this function and return your own extension of {@link TAPJob}. + * </p> + * + * @see tap.TAPFactory#createTAPJob(javax.servlet.http.HttpServletRequest, uws.job.user.JobOwner) + */ @Override protected TAPJob createTAPJob(final String jobId, final JobOwner owner, final TAPParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws UWSException{ try{ @@ -70,11 +247,18 @@ public abstract class AbstractTAPFactory extends TAPFactory { } } - @Override - public ADQLExecutor createADQLExecutor() throws TAPException{ - return new ADQLExecutor(service); - } - + /** + * <p>This implementation extracts standard TAP parameters from the given request.</p> + * + * <p> + * Non-standard TAP parameters are added in a map inside the returned {@link TAPParameters} object + * and are accessible with {@link TAPParameters#get(String)} and {@link TAPParameters#getAdditionalParameters()}. + * However, if you want to manage them in another way, you must extend {@link TAPParameters} and override + * this function in order to return an instance of your extension. + * </p> + * + * @see tap.TAPFactory#createTAPParameters(javax.servlet.http.HttpServletRequest) + */ @Override protected TAPParameters createTAPParameters(final HttpServletRequest request) throws UWSException{ try{ @@ -84,6 +268,18 @@ public abstract class AbstractTAPFactory extends TAPFactory { } } + /** + * <p>This implementation extracts standard TAP parameters from the given request.</p> + * + * <p> + * Non-standard TAP parameters are added in a map inside the returned {@link TAPParameters} object + * and are accessible with {@link TAPParameters#get(String)} and {@link TAPParameters#getAdditionalParameters()}. + * However, if you want to manage them in another way, you must extend {@link TAPParameters} and override + * this function in order to return an instance of your extension. + * </p> + * + * @see tap.TAPFactory#createTAPParameters(javax.servlet.http.HttpServletRequest) + */ @Override protected TAPParameters createTAPParameters(final Map<String,Object> params) throws UWSException{ try{ @@ -93,19 +289,4 @@ public abstract class AbstractTAPFactory extends TAPFactory { } } - @Override - public ADQLQueryFactory createQueryFactory() throws TAPException{ - return new ADQLQueryFactory(); - } - - @Override - protected QueryChecker createQueryChecker(final Collection<TAPTable> tables) throws TAPException{ - return new DBChecker(tables); - } - - @Override - public Uploader createUploader(final DBConnection dbConn) throws TAPException{ - return new Uploader(service, dbConn); - } - } diff --git a/src/tap/TAPFactory.java b/src/tap/TAPFactory.java index 3ef5ef93b0d4ac2af9a1229aef8773285209a81f..aac9b3ec88fb405daba92b777c3243cc5d88fcca 100644 --- a/src/tap/TAPFactory.java +++ b/src/tap/TAPFactory.java @@ -20,9 +20,6 @@ package tap; * Astronomisches Rechen Institut (ARI) */ -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -30,9 +27,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import tap.db.DBConnection; -import tap.metadata.TAPMetadata; import tap.metadata.TAPSchema; -import tap.metadata.TAPTable; import tap.parameters.TAPParameters; import tap.upload.Uploader; import uws.UWSException; @@ -47,12 +42,37 @@ import uws.service.UWSService; import uws.service.backup.UWSBackupManager; import adql.parser.ADQLQueryFactory; import adql.parser.QueryChecker; -import adql.translator.ADQLTranslator; +import adql.query.ADQLQuery; +/** + * <p>Let build essential objects of the TAP service.</p> + * + * <p>Basically, it means answering to the following questions:</p> + * <ul> + * <li>how to connect to the database? <i>({@link DBConnection})</i></li> + * <li>which UWS implementation (default implementation provided by default) to use? <i>({@link UWSService})</i></li> + * <li>whether and how UWS/asynchronous jobs must be backuped and restored? <i>({@link UWSBackupManager})</i></li> + * <li>how to create asynchronous jobs? <i>({@link TAPJob})</i></li> + * <li>whether and how tables must be updated? <i>({@link Uploader})</i></li> + * <li>how to check ADQL queries? <i>({@link QueryChecker})</i></li> + * </ul> + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (08/2014) + */ public abstract class TAPFactory extends AbstractUWSFactory { + /** Connection to the TAP service ; it provides all important service configuration information. */ protected final ServiceConnection service; + /** + * Build a basic {@link TAPFactory}. + * Nothing is done except setting the service connection. + * + * @param service Configuration of the TAP service. <i>MUST NOT be NULL</i> + * + * @throws NullPointerException If the given {@link ServiceConnection} is NULL. + */ protected TAPFactory(final ServiceConnection service) throws NullPointerException{ if (service == null) throw new NullPointerException("Can not create a TAPFactory without a ServiceConnection instance !"); @@ -60,25 +80,192 @@ public abstract class TAPFactory extends AbstractUWSFactory { this.service = service; } - public abstract ADQLTranslator createADQLTranslator() throws TAPException; - - protected abstract DBConnection createDBConnection() throws TAPException; + /* ******************* */ + /* DATABASE CONNECTION */ + /* ******************* */ + /** + * <p>Get a free database connection.</p> + * + * <p> + * <i>Free</i> means this connection is not currently in use and will be exclusively dedicated to the function/process/thread + * which has asked for it by calling this function. + * </p> + * + * <p><i>Note: + * This function can create on the fly a new connection OR get a free one from a connection pool. Considering the + * creation time of a database connection, the second way is recommended. + * </i></p> + * + * <p><b>IMPORTANT:</b> + * The returned connection MUST be freed after having used it. + * </p> + * + * <p><i><b>WARNING:</b> + * Some implementation may free the connection automatically when not used for a specific time. + * So, do not forget to free the connection after use! + * </i></p> + * + * @param jobID ID of the job/thread/process which has asked for this connection. <i>note: The returned connection must then be identified thanks to this ID.</i> + * + * @return A new and free connection to the database. <b>MUST BE NOT NULL, or otherwise a TAPException should be returned.</b> + * + * @throws TAPException If there is any error while getting a free connection. + */ public abstract DBConnection getConnection(final String jobID) throws TAPException; + /** + * <p>Free the given connection.</p> + * + * <p> + * This function is called by the TAP library when a job/thread does not need this connection any more. It aims + * to free resources associated to the given database connection. + * </p> + * + * <p><i>Note: + * This function can just close definitely the connection OR give it back to a connection pool. The implementation is + * here totally free! + * </i></p> + * + * @param conn The connection to close. + */ public abstract void freeConnection(final DBConnection conn); /** - * @return <=0 in case of problem, >0 otherwise. + * <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>Note: + * If the implementation of this factory creates connections on the fly, the value 1 (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>). */ public abstract int countFreeConnections(); - public UWSService createUWS() throws TAPException{ - return new UWSService(this, this.service.getFileManager(), this.service.getLogger()); - } + /* *************** */ + /* ADQL MANAGEMENT */ + /* *************** */ + + /** + * <p>Create the object able to execute an ADQL query and to write and to format its result.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory} + * </i></p> + * + * @return An ADQL executor. + * + * @throws TAPException If any error occurs while creating an ADQL executor. + */ + public abstract ADQLExecutor createADQLExecutor() throws TAPException; + + /** + * <p>Create a factory able to build every part of an {@link ADQLQuery} object.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory} + * </i></p> + * + * @return An {@link ADQLQuery} factory. + * + * @throws TAPException If any error occurs while creating the factory. + */ + public abstract ADQLQueryFactory createQueryFactory() throws TAPException; + /** + * <p>Create an object able to check the consistency between the ADQL query and the database. + * That's to say, it checks whether the tables and columns used in the query really exist + * in the database.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory} + * </i></p> + * + * @param uploadSchema ADQL schema containing the description of all uploaded tables. + * + * @return A query checker. + * + * @throws TAPException If any error occurs while creating a query checker. + */ + public abstract QueryChecker createQueryChecker(final TAPSchema uploadSchema) throws TAPException; + + /* ****** */ + /* UPLOAD */ + /* ****** */ + + /** + * <p>Create an object able to manage the creation of submitted user tables (in VOTable) into the database.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory}. + * </i></p> + * + * @param dbConn The database connection which has requested an {@link Uploader}. + * + * @return An {@link Uploader}. + * + * @throws TAPException If any error occurs while creating an {@link Uploader} instance. + */ + public abstract Uploader createUploader(final DBConnection dbConn) throws TAPException; + + /* ************** */ + /* UWS MANAGEMENT */ + /* ************** */ + + /** + * <p>Create the object which will manage the asynchronous resource of the TAP service. + * This resource is a UWS service.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory}. + * </i></p> + * + * @return A UWS service which will be the asynchronous resource of this TAP service. + * + * @throws TAPException If any error occurs while creating this UWS service. + */ + public abstract UWSService createUWS() throws TAPException; + + /** + * <p>Create the object which will manage the backup and restoration of all asynchronous jobs.</p> + * + * <p><i>Note: + * This function may return NULL. If it does, asynchronous jobs won't be backuped. + * </i></p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory}. + * </i></p> + * + * @param uws The UWS service which has to be backuped and restored. + * + * @return The backup manager to use. <i>MAY be NULL</i> + * + * @throws TAPException If any error occurs while creating this backup manager. + */ public abstract UWSBackupManager createUWSBackupManager(final UWSService uws) throws TAPException; + /** + * <p>Creates a (PENDING) UWS job from the given HTTP request.</p> + * + * <p> + * This implementation just call {@link #createTAPJob(HttpServletRequest, JobOwner)} + * with the given request, in order to ensure that the returned object is always a {@link TAPJob}. + * </p> + * + * @see uws.service.AbstractUWSFactory#createJob(javax.servlet.http.HttpServletRequest, uws.job.user.JobOwner) + * @see #createTAPJob(HttpServletRequest, JobOwner) + */ @Override public final UWSJob createJob(HttpServletRequest request, JobOwner owner) throws UWSException{ if (!service.isAvailable()) @@ -87,8 +274,41 @@ public abstract class TAPFactory extends AbstractUWSFactory { return createTAPJob(request, owner); } + /** + * <p>Create a PENDING asynchronous job from the given HTTP request.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory}. + * </i></p> + * + * @param request Request which contains all parameters needed to set correctly the asynchronous job to create. + * @param owner The user which has requested the job creation. + * + * @return A new PENDING asynchronous job. + * + * @throws UWSException If any error occurs while reading the parameters in the request or while creating the job. + */ protected abstract TAPJob createTAPJob(final HttpServletRequest request, final JobOwner owner) throws UWSException; + /** + * <p>Creates a UWS job with the following attributes.</p> + * + * <p> + * This implementation just call {@link #createTAPJob(String, JobOwner, TAPParameters, long, long, long, List, ErrorSummary)} + * with the given parameters, in order to ensure that the returned object is always a {@link TAPJob}. + * </p> + * + * <p><i>Note 1: + * This function is mainly used to restore a UWS job at the UWS initialization. + * </i></p> + * + * <p><i>Note 2: + * The job phase is chosen automatically from the given job attributes (i.e. no endTime => PENDING, no result and no error => ABORTED, ...). + * </i></p> + * + * @see uws.service.AbstractUWSFactory#createJob(java.lang.String, uws.job.user.JobOwner, uws.job.parameters.UWSParameters, long, long, long, java.util.List, uws.job.ErrorSummary) + * @see #createTAPJob(String, JobOwner, TAPParameters, long, long, long, List, ErrorSummary) + */ @Override public final UWSJob createJob(String jobId, JobOwner owner, final UWSParameters params, long quote, long startTime, long endTime, List<Result> results, ErrorSummary error) throws UWSException{ if (!service.isAvailable()) @@ -97,8 +317,39 @@ public abstract class TAPFactory extends AbstractUWSFactory { return createTAPJob(jobId, owner, (TAPParameters)params, quote, startTime, endTime, results, error); } + /** + * <p>Create a PENDING asynchronous job with the given parameters.</p> + * + * <p><i>Note: + * A default implementation is provided in {@link AbstractTAPFactory}. + * </i></p> + * + * @param jobID ID of the job (NOT NULL). + * @param owner Owner of the job. + * @param params List of all input job parameters. + * @param quote Its quote (in seconds). + * @param startTime Date/Time of the start of this job. + * @param endTime Date/Time of the end of this job. + * @param results All results of this job. + * @param error The error which ended the job to create. + * + * @return A new PENDING asynchronous job. + * + * @throws UWSException If there is an error while creating the job. + */ protected abstract TAPJob createTAPJob(final String jobId, final JobOwner owner, final TAPParameters params, final long quote, final long startTime, final long endTime, final List<Result> results, final ErrorSummary error) throws UWSException; + /** + * <p>Create the thread which will execute the task described by the given UWSJob instance.</p> + * + * <p> + * This function is definitely implemented here and can not be overrided. The processing of + * an ADQL query must always be the same in a TAP service ; it is completely done by {@link AsyncThread}. + * </p> + * + * @see uws.service.UWSFactory#createJobThread(uws.job.UWSJob) + * @see AsyncThread + */ @Override public final JobThread createJobThread(final UWSJob job) throws UWSException{ try{ @@ -108,45 +359,67 @@ public abstract class TAPFactory extends AbstractUWSFactory { } } - public abstract ADQLExecutor createADQLExecutor() throws TAPException; - /** - * Extracts the parameters from the given request (multipart or not). - * This function is used only to set UWS parameters, not to create a TAP query (for that, see {@link TAPParameters}). + * <p>Extract the parameters from the given request (multipart or not).</p> + * + * <p> + * This function is used only to create the set of parameters for a TAP job (synchronous or asynchronous). + * Thus, it just call {@link #createTAPParameters(HttpServletRequest)} with the given request, in order to ensure + * that the returned object is always a {@link TAPParameters}. + * </p> * * @see uws.service.AbstractUWSFactory#extractParameters(javax.servlet.http.HttpServletRequest, uws.service.UWS) + * @see #createTAPParameters(HttpServletRequest) */ @Override public final UWSParameters createUWSParameters(HttpServletRequest request) throws UWSException{ return createTAPParameters(request); } + /** + * <p>Extract all the TAP parameters from the given HTTP request (multipart or not) and return them.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory}. + * </i></p> + * + * @param request The HTTP request containing the TAP parameters to extract. + * + * @return An object gathering all successfully extracted TAP parameters. + * + * @throws UWSException If any error occurs while extracting the parameters. + */ protected abstract TAPParameters createTAPParameters(final HttpServletRequest request) throws UWSException; + /** + * <p>Identify and gather all identified parameters of the given map inside a {@link TAPParameters} object.</p> + * + * <p> + * This implementation just call {@link #createTAPParameters(Map)} with the given map, in order to ensure + * that the returned object is always a {@link TAPParameters}. + * </p> + * + * @see uws.service.AbstractUWSFactory#createUWSParameters(java.util.Map) + * @see #createTAPParameters(Map) + */ @Override public final UWSParameters createUWSParameters(Map<String,Object> params) throws UWSException{ return createTAPParameters(params); } + /** + * <p>Identify all TAP parameters and gather them inside a {@link TAPParameters} object.</p> + * + * <p><i>Note: + * A default implementation is provided by {@link AbstractTAPFactory}. + * </i></p> + * + * @param params Map containing all parameters. + * + * @return An object gathering all successfully identified TAP parameters. + * + * @throws UWSException If any error occurs while creating the {@link TAPParameters} object. + */ protected abstract TAPParameters createTAPParameters(final Map<String,Object> params) throws UWSException; - public abstract ADQLQueryFactory createQueryFactory() throws TAPException; - - public final QueryChecker createQueryChecker(final TAPSchema uploadSchema) throws TAPException{ - TAPMetadata meta = service.getTAPMetadata(); - ArrayList<TAPTable> tables = new ArrayList<TAPTable>(meta.getNbTables()); - Iterator<TAPTable> it = meta.getTables(); - while(it.hasNext()) - tables.add(it.next()); - if (uploadSchema != null){ - for(TAPTable table : uploadSchema) - tables.add(table); - } - return createQueryChecker(tables); - } - - protected abstract QueryChecker createQueryChecker(final Collection<TAPTable> tables) throws TAPException; - - public abstract Uploader createUploader(final DBConnection dbConn) throws TAPException; - }