diff --git a/src/adql/parser/.gitignore b/src/adql/parser/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5e75933c6a3ba2a311cd3cd309c6159f5c3e1845 --- /dev/null +++ b/src/adql/parser/.gitignore @@ -0,0 +1,3 @@ +/ADQLParser.java +/ADQLParserConstants.java +/ADQLParserTokenManager.java diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java index 6b70b0cfcd6ec1c04531de85cf26b3a625b3870d..a7aee6ffbfa3456eedd0415ac960c57d4335cb30 100644 --- a/src/tap/ADQLExecutor.java +++ b/src/tap/ADQLExecutor.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-2013 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institute (ARI) */ @@ -26,6 +26,7 @@ import java.sql.SQLException; import javax.servlet.http.HttpServletResponse; +import tap.data.TableIterator; import tap.db.DBConnection; import tap.db.DBException; import tap.formatter.OutputFormat; @@ -42,20 +43,17 @@ import adql.parser.ADQLQueryFactory; import adql.parser.ParseException; import adql.parser.QueryChecker; import adql.query.ADQLQuery; -import adql.translator.ADQLTranslator; import adql.translator.TranslationException; /** * * - * @author Grégory Mantelet (CDS;ARI) - gmantele@ari.uni-heidelberg.de - * @version 1.1 (12/2013) - * - * @param <R> + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) */ -public class ADQLExecutor< R > { +public class ADQLExecutor { - protected final ServiceConnection<R> service; + protected final ServiceConnection service; protected final TAPLog logger; protected Thread thread; @@ -63,10 +61,10 @@ public class ADQLExecutor< R > { protected HttpServletResponse response; protected TAPExecutionReport report; - private DBConnection<R> dbConn = null; + private DBConnection dbConn = null; protected TAPSchema uploadSchema = null; - public ADQLExecutor(final ServiceConnection<R> service){ + public ADQLExecutor(final ServiceConnection service){ this.service = service; this.logger = service.getLogger(); } @@ -79,76 +77,21 @@ public class ADQLExecutor< R > { return report; } - public boolean hasUploadedTables(){ - return (uploadSchema != null) && (uploadSchema.getNbTables() > 0); - } - - protected final DBConnection<R> getDBConnection() throws TAPException{ - return (dbConn != null) ? dbConn : (dbConn = service.getFactory().createDBConnection((report != null) ? report.jobID : null)); - } - - public final void closeDBConnection() throws TAPException{ - if (dbConn != null){ - dbConn.close(); - dbConn = null; - } - } - - private final void uploadTables() throws TAPException{ - TableLoader[] tables = tapParams.getTableLoaders(); - if (tables.length > 0){ - logger.info("JOB " + report.jobID + "\tLoading uploaded tables (" + tables.length + ")..."); - long start = System.currentTimeMillis(); - try{ - /* TODO Problem with the DBConnection! One is created here for the Uploader (and dbConn is set) and closed by its uploadTables function (but dbConn is not set to null). - * Ideally, the connection should not be close, or at least dbConn should be set to null just after. */ - uploadSchema = service.getFactory().createUploader(getDBConnection()).upload(tables); - }finally{ - TAPParameters.deleteUploadedTables(tables); - report.setDuration(ExecutionProgression.UPLOADING, System.currentTimeMillis() - start); - } - } - - } - - private final R executeADQL() throws ParseException, InterruptedException, TranslationException, SQLException, TAPException, UWSException{ - long start; - - tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.PARSING); - start = System.currentTimeMillis(); - ADQLQuery adql = parseADQL(); - report.setDuration(ExecutionProgression.PARSING, System.currentTimeMillis() - start); - - if (thread.isInterrupted()) - throw new InterruptedException(); - - report.resultingColumns = adql.getResultingColumns(); - - final int limit = adql.getSelect().getLimit(); - final Integer maxRec = tapParams.getMaxRec(); - if (maxRec != null && maxRec > -1){ - if (limit <= -1 || limit > maxRec) - adql.getSelect().setLimit(maxRec + 1); - } - - tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.TRANSLATING); - start = System.currentTimeMillis(); - String sqlQuery = translateADQL(adql); - report.setDuration(ExecutionProgression.TRANSLATING, System.currentTimeMillis() - start); - report.sqlTranslation = sqlQuery; - - if (thread.isInterrupted()) - throw new InterruptedException(); + protected OutputFormat getFormatter() throws TAPException{ + // Search for the corresponding formatter: + String format = tapParams.getFormat(); + OutputFormat formatter = service.getOutputFormat((format == null) ? "votable" : format); + if (format != null && formatter == null) + formatter = service.getOutputFormat("votable"); - tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.EXECUTING_SQL); - start = System.currentTimeMillis(); - R result = executeQuery(sqlQuery, adql); - report.setDuration(ExecutionProgression.EXECUTING_SQL, System.currentTimeMillis() - start); + // Format the result: + if (formatter == null) + throw new TAPException("Impossible to format the query result: no formatter has been found for the given MIME type \"" + format + "\" and for the default MIME type \"votable\" (short form) !"); - return result; + return formatter; } - public final TAPExecutionReport start(final AsyncThread<R> thread) throws TAPException, UWSException, InterruptedException, ParseException, TranslationException, SQLException{ + public final TAPExecutionReport start(final AsyncThread thread) throws UWSException, InterruptedException{ if (this.thread != null || this.report != null) throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "This ADQLExecutor has already been executed !"); @@ -162,7 +105,7 @@ public class ADQLExecutor< R > { return start(); } - public final TAPExecutionReport start(final Thread thread, final String jobId, final TAPParameters params, final HttpServletResponse response) throws TAPException, UWSException, InterruptedException, ParseException, TranslationException, SQLException{ + public final TAPExecutionReport start(final Thread thread, final String jobId, final TAPParameters params, final HttpServletResponse response) throws TAPException, InterruptedException{ if (this.thread != null || this.report != null) throw new TAPException("This ADQLExecutor has already been executed !"); @@ -177,7 +120,10 @@ public class ADQLExecutor< R > { protected final TAPExecutionReport start() throws TAPException, UWSException, InterruptedException, ParseException, TranslationException, SQLException{ long start = System.currentTimeMillis(); try{ - // Upload tables if needed: + // Get a "database" connection: + dbConn = service.getFactory().createDBConnection(report.jobID); + + // 1. UPLOAD TABLES, if needed: if (tapParams != null && tapParams.getTableLoaders() != null && tapParams.getTableLoaders().length > 0){ tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.UPLOADING); uploadTables(); @@ -186,18 +132,24 @@ public class ADQLExecutor< R > { if (thread.isInterrupted()) throw new InterruptedException(); - // Parse, translate in SQL and execute the ADQL query: - R queryResult = executeADQL(); - if (queryResult == null || thread.isInterrupted()) + // 2. PARSE THE ADQL QUERY: + tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.PARSING); + ADQLQuery adqlQuery = parseADQL(); + + if (adqlQuery == null || thread.isInterrupted()) throw new InterruptedException(); - // Write the result: + // 3. EXECUTE THE ADQL QUERY: + tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.EXECUTING_ADQL); + TableIterator queryResult = executeADQL(adqlQuery); + + // 4. WRITE RESULT: tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.WRITING_RESULT); writeResult(queryResult); + // Report the COMPLETED status: logger.info("JOB " + report.jobID + " COMPLETED"); tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.FINISHED); - report.success = true; return report; @@ -211,7 +163,10 @@ public class ADQLExecutor< R > { logger.error("JOB " + report.jobID + "\tCan not drop uploaded tables !", e); } try{ - closeDBConnection(); + if (dbConn != null){ + dbConn.close(); + dbConn = null; + } }catch(TAPException e){ logger.error("JOB " + report.jobID + "\tCan not close the DB connection !", e); } @@ -220,7 +175,27 @@ public class ADQLExecutor< R > { } } + private final void uploadTables() throws TAPException{ + // Fetch the tables to upload: + TableLoader[] tables = tapParams.getTableLoaders(); + + // Upload them, if needed: + if (tables.length > 0){ + logger.info("JOB " + report.jobID + "\tLoading uploaded tables (" + tables.length + ")..."); + long start = System.currentTimeMillis(); + try{ + /* TODO Problem with the DBConnection! One is created here for the Uploader (and dbConn is set) and closed by its uploadTables function (but dbConn is not set to null). + * Ideally, the connection should not be close, or at least dbConn should be set to null just after. */ + uploadSchema = service.getFactory().createUploader(dbConn).upload(tables); + }finally{ + TAPParameters.deleteUploadedTables(tables); + report.setDuration(ExecutionProgression.UPLOADING, System.currentTimeMillis() - start); + } + } + } + protected ADQLQuery parseADQL() throws ParseException, InterruptedException, TAPException{ + long start = System.currentTimeMillis(); ADQLQueryFactory queryFactory = service.getFactory().createQueryFactory(); QueryChecker queryChecker = service.getFactory().createQueryChecker(uploadSchema); ADQLParser parser; @@ -230,22 +205,21 @@ public class ADQLExecutor< R > { parser = new ADQLParser(queryChecker, queryFactory); parser.setCoordinateSystems(service.getCoordinateSystems()); parser.setDebug(false); - //logger.info("Job "+report.jobID+" - 1/5 Parsing ADQL...."); - return parser.parseQuery(tapParams.getQuery()); - } - - protected String translateADQL(ADQLQuery query) throws TranslationException, InterruptedException, TAPException{ - ADQLTranslator translator = service.getFactory().createADQLTranslator(); - //logger.info("Job "+report.jobID+" - 2/5 Translating ADQL..."); - return translator.translate(query); + ADQLQuery query = parser.parseQuery(tapParams.getQuery()); + final int limit = query.getSelect().getLimit(); + final Integer maxRec = tapParams.getMaxRec(); + if (maxRec != null && maxRec > -1){ + if (limit <= -1 || limit > maxRec) + query.getSelect().setLimit(maxRec + 1); + } + report.setDuration(ExecutionProgression.PARSING, System.currentTimeMillis() - start); + report.resultingColumns = query.getResultingColumns(); + return query; } - protected R executeQuery(String sql, ADQLQuery adql) throws SQLException, InterruptedException, TAPException{ - //logger.info("Job "+report.jobID+" - 3/5 Creating DBConnection...."); - DBConnection<R> dbConn = getDBConnection(); - //logger.info("Job "+report.jobID+" - 4/5 Executing query...\n"+sql); + protected TableIterator executeADQL(ADQLQuery adql) throws SQLException, InterruptedException, TAPException{ final long startTime = System.currentTimeMillis(); - R result = dbConn.executeQuery(sql, adql); + TableIterator result = dbConn.executeQuery(adql); if (result == null) logger.info("JOB " + report.jobID + " - QUERY ABORTED AFTER " + (System.currentTimeMillis() - startTime) + " MS !"); else @@ -253,22 +227,8 @@ public class ADQLExecutor< R > { return result; } - protected OutputFormat<R> getFormatter() throws TAPException{ - // Search for the corresponding formatter: - String format = tapParams.getFormat(); - OutputFormat<R> formatter = service.getOutputFormat((format == null) ? "votable" : format); - if (format != null && formatter == null) - formatter = service.getOutputFormat("votable"); - - // Format the result: - if (formatter == null) - throw new TAPException("Impossible to format the query result: no formatter has been found for the given MIME type \"" + format + "\" and for the default MIME type \"votable\" (short form) !"); - - return formatter; - } - - protected final void writeResult(R queryResult) throws InterruptedException, TAPException, UWSException{ - OutputFormat<R> formatter = getFormatter(); + protected final void writeResult(TableIterator queryResult) throws InterruptedException, TAPException, UWSException{ + OutputFormat formatter = getFormatter(); // Synchronous case: if (response != null){ @@ -300,7 +260,7 @@ public class ADQLExecutor< R > { } } - protected void writeResult(R queryResult, OutputFormat<R> formatter, OutputStream output) throws InterruptedException, TAPException{ + protected void writeResult(TableIterator queryResult, OutputFormat formatter, OutputStream output) throws InterruptedException, TAPException{ //logger.info("Job "+report.jobID+" - 5/5 Writing result file..."); formatter.writeResult(queryResult, output, report, thread); } @@ -308,15 +268,13 @@ public class ADQLExecutor< R > { protected void dropUploadedTables() throws TAPException{ if (uploadSchema != null){ // Drop all uploaded tables: - DBConnection<R> dbConn = getDBConnection(); for(TAPTable t : uploadSchema){ try{ - dbConn.dropTable(t); + dbConn.dropUploadedTable(t.getDBName()); }catch(DBException dbe){ logger.error("JOB " + report.jobID + "\tCan not drop the table \"" + t.getDBName() + "\" (in adql \"" + t.getADQLName() + "\") from the database !", dbe); } } - closeDBConnection(); } } diff --git a/src/tap/AbstractTAPFactory.java b/src/tap/AbstractTAPFactory.java index 91bad42e857dae762b36d571b0a589b5bdf8c75d..73de9b13352db805a443a2eaed37231fee103955 100644 --- a/src/tap/AbstractTAPFactory.java +++ b/src/tap/AbstractTAPFactory.java @@ -32,33 +32,27 @@ import tap.metadata.TAPMetadata; import tap.metadata.TAPSchema; import tap.metadata.TAPTable; import tap.parameters.TAPParameters; - import tap.upload.Uploader; - import uws.UWSException; - import uws.job.ErrorSummary; 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.AbstractUWSFactory; import uws.service.UWSService; import uws.service.backup.UWSBackupManager; import adql.db.DBChecker; import adql.db.DBTable; - import adql.parser.ADQLQueryFactory; import adql.parser.QueryChecker; -public abstract class AbstractTAPFactory< R > extends AbstractUWSFactory implements TAPFactory<R> { +public abstract class AbstractTAPFactory extends AbstractUWSFactory implements TAPFactory { - protected final ServiceConnection<R> service; + protected final ServiceConnection service; - protected AbstractTAPFactory(ServiceConnection<R> service) throws NullPointerException{ + protected AbstractTAPFactory(ServiceConnection service) throws NullPointerException{ if (service == null) throw new NullPointerException("Can not create a TAPFactory without a ServiceConnection instance !"); @@ -102,14 +96,15 @@ public abstract class AbstractTAPFactory< R > extends AbstractUWSFactory impleme @Override public final JobThread createJobThread(final UWSJob job) throws UWSException{ try{ - return new AsyncThread<R>((TAPJob)job, createADQLExecutor()); + return new AsyncThread((TAPJob)job, createADQLExecutor()); }catch(TAPException te){ throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, te, "Impossible to create an AsyncThread !"); } } - public ADQLExecutor<R> createADQLExecutor() throws TAPException{ - return new ADQLExecutor<R>(service); + @Override + public ADQLExecutor createADQLExecutor() throws TAPException{ + return new ADQLExecutor(service); } /** @@ -155,7 +150,8 @@ public abstract class AbstractTAPFactory< R > extends AbstractUWSFactory impleme return new DBChecker(tables); } - public Uploader createUploader(final DBConnection<R> dbConn) throws TAPException{ + @Override + public Uploader createUploader(final DBConnection dbConn) throws TAPException{ return new Uploader(service, dbConn); } diff --git a/src/tap/AsyncThread.java b/src/tap/AsyncThread.java index 4581c0900f4cb93f8b9814e9ead36d9fc690a33d..7d0d0489da1e6cd636ab61df91a37cf1d4e6d094 100644 --- a/src/tap/AsyncThread.java +++ b/src/tap/AsyncThread.java @@ -19,17 +19,16 @@ package tap; * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS) */ -import adql.parser.ParseException; -import adql.translator.TranslationException; import uws.UWSException; - import uws.job.JobThread; +import adql.parser.ParseException; +import adql.translator.TranslationException; -public class AsyncThread< R > extends JobThread { +public class AsyncThread extends JobThread { - protected final ADQLExecutor<R> executor; + protected final ADQLExecutor executor; - public AsyncThread(TAPJob j, ADQLExecutor<R> executor) throws UWSException{ + public AsyncThread(TAPJob j, ADQLExecutor executor) throws UWSException{ super(j, "Execute the ADQL query of the TAP request " + j.getJobId()); this.executor = executor; } diff --git a/src/tap/ExecutionProgression.java b/src/tap/ExecutionProgression.java index 618d2104813c7d0584be6f136e4b4390b3ef95a6..4086ccdec638ef9b3139e7dae75949c38e291884 100644 --- a/src/tap/ExecutionProgression.java +++ b/src/tap/ExecutionProgression.java @@ -16,9 +16,16 @@ 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ +/** + * Let describe the current status of a job execution. + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) + */ public enum ExecutionProgression{ - PENDING, UPLOADING, PARSING, TRANSLATING, EXECUTING_SQL, WRITING_RESULT, FINISHED; + PENDING, UPLOADING, PARSING, EXECUTING_ADQL, WRITING_RESULT, FINISHED; } diff --git a/src/tap/ServiceConnection.java b/src/tap/ServiceConnection.java index 40e3745205e86a5a6dcea1cdd9fa647eb5459935..6a2782b7f4ba6190f677d9afe681347ee48021b0 100644 --- a/src/tap/ServiceConnection.java +++ b/src/tap/ServiceConnection.java @@ -23,16 +23,12 @@ import java.util.Collection; import java.util.Iterator; import tap.file.TAPFileManager; - import tap.formatter.OutputFormat; - import tap.log.TAPLog; - import tap.metadata.TAPMetadata; - import uws.service.UserIdentifier; -public interface ServiceConnection< R > { +public interface ServiceConnection { public static enum LimitUnit{ rows, bytes; @@ -70,12 +66,12 @@ public interface ServiceConnection< R > { public TAPLog getLogger(); - public TAPFactory<R> getFactory(); + public TAPFactory getFactory(); public TAPFileManager getFileManager(); - public Iterator<OutputFormat<R>> getOutputFormats(); + public Iterator<OutputFormat> getOutputFormats(); - public OutputFormat<R> getOutputFormat(final String mimeOrAlias); + public OutputFormat getOutputFormat(final String mimeOrAlias); } diff --git a/src/tap/TAPExecutionReport.java b/src/tap/TAPExecutionReport.java index bf95af1a449dd836cc4b16f1c65960e90d7db4b4..45ce99b893f9d2e214bc0404b64b789c55d15a7f 100644 --- a/src/tap/TAPExecutionReport.java +++ b/src/tap/TAPExecutionReport.java @@ -16,90 +16,163 @@ 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ -import adql.db.DBColumn; - import tap.parameters.TAPParameters; +import adql.db.DBColumn; +/** + * <p>Report the execution (including the parsing and the output writing) of an ADQL query. + * It gives information on the job parameters, the job ID, whether it is a synchronous task or not, times of each execution step (uploading, parsing, executing and writing), + * the resulting columns and the success or not of the execution.</p> + * + * <p>This report is completely filled by {@link ADQLExecutor}, and aims to be used/read only at the end of the job or when it is definitely finished.</p> + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) + */ public class TAPExecutionReport { + /** ID of the job whose the execution is reported here. */ public final String jobID; + + /** Indicate whether this execution is done in a synchronous or asynchronous job. */ public final boolean synchronous; + + /** List of all parameters provided in the user request. */ public final TAPParameters parameters; - public String sqlTranslation = null; + /** List of all resulting columns. <i>Empty array, if not yet known.</i> */ public DBColumn[] resultingColumns = new DBColumn[0]; - protected final long[] durations = new long[]{-1,-1,-1,-1,-1}; + /** Duration of all execution steps. <i>For the moment only 4 steps (in the order): uploading, parsing, executing and writing.</i> */ + protected final long[] durations = new long[]{-1,-1,-1,-1}; + + /** Total duration of the job execution. */ protected long totalDuration = -1; + /** Indicate whether this job has ended successfully or not. <i>At the beginning or while executing, this field is always FALSE.</i> */ public boolean success = false; + /** + * Build an empty execution report. + * + * @param jobID ID of the job whose the execution must be described here. + * @param synchronous <i>true</i> if the job is synchronous, <i>false</i> otherwise. + * @param params List of all parameters provided by the user for the execution. + */ public TAPExecutionReport(final String jobID, final boolean synchronous, final TAPParameters params){ this.jobID = jobID; this.synchronous = synchronous; parameters = params; } + /** + * <p>Map the execution progression with an index inside the {@link #durations} array.</p> + * + * <p><i><b>Warning:</b> for the moment, only {@link ExecutionProgression#UPLOADING}, {@link ExecutionProgression#PARSING}, + * {@link ExecutionProgression#EXECUTING_ADQL} and {@link ExecutionProgression#WRITING_RESULT} are managed.</i></p> + * + * @param tapProgression Execution progression. + * + * @return Index in the array {@link #durations}, or -1 if the given execution progression is not managed. + */ protected int getIndexDuration(final ExecutionProgression tapProgression){ switch(tapProgression){ case UPLOADING: return 0; case PARSING: return 1; - case TRANSLATING: + case EXECUTING_ADQL: return 2; - case EXECUTING_SQL: - return 3; case WRITING_RESULT: - return 4; + return 3; default: return -1; } } - public final long getDuration(final ExecutionProgression tapProgression){ - int indDuration = getIndexDuration(tapProgression); + /** + * Get the duration corresponding to the given job execution step. + * + * @param tapStep Job execution step. + * + * @return The corresponding duration (in ms), or -1 if this step has not been (yet) processed. + * + * @see #getIndexDuration(ExecutionProgression) + */ + public final long getDuration(final ExecutionProgression tapStep){ + int indDuration = getIndexDuration(tapStep); if (indDuration < 0 || indDuration >= durations.length) return -1; else return durations[indDuration]; } - public final void setDuration(final ExecutionProgression tapProgression, final long duration){ - int indDuration = getIndexDuration(tapProgression); + /** + * Set the duration corresponding to the given execution step. + * + * @param tapStep Job execution step. + * @param duration Duration (in ms) of the given execution step. + */ + public final void setDuration(final ExecutionProgression tapStep, final long duration){ + int indDuration = getIndexDuration(tapStep); if (indDuration < 0 || indDuration >= durations.length) return; else durations[indDuration] = duration; } + /** + * Get the execution of the UPLOAD step. + * @return Duration (in ms). + * @see #getDuration(ExecutionProgression) + */ public final long getUploadDuration(){ return getDuration(ExecutionProgression.UPLOADING); } + /** + * Get the execution of the PARSE step. + * @return Duration (in ms). + * @see #getDuration(ExecutionProgression) + */ public final long getParsingDuration(){ return getDuration(ExecutionProgression.PARSING); } - public final long getTranslationDuration(){ - return getDuration(ExecutionProgression.TRANSLATING); - } - + /** + * Get the execution of the EXECUTION step. + * @return Duration (in ms). + * @see #getDuration(ExecutionProgression) + */ public final long getExecutionDuration(){ - return getDuration(ExecutionProgression.EXECUTING_SQL); + return getDuration(ExecutionProgression.EXECUTING_ADQL); } + /** + * Get the execution of the FORMAT step. + * @return Duration (in ms). + * @see #getDuration(ExecutionProgression) + */ public final long getFormattingDuration(){ return getDuration(ExecutionProgression.WRITING_RESULT); } + /** + * Get the total duration of the job execution. + * @return Duration (in ms). + */ public final long getTotalDuration(){ return totalDuration; } + /** + * Set the total duration of the job execution. + * @param duration Duration (in ms) to set. + */ public final void setTotalDuration(final long duration){ totalDuration = duration; } diff --git a/src/tap/TAPFactory.java b/src/tap/TAPFactory.java index 785be4795659e53d11bc2b0ffecf32477784f5dd..9911edd183039112cf0f2d6eaf93102311a36f55 100644 --- a/src/tap/TAPFactory.java +++ b/src/tap/TAPFactory.java @@ -20,30 +20,23 @@ package tap; */ import tap.db.DBConnection; - import tap.metadata.TAPSchema; - import tap.upload.Uploader; - import uws.UWSException; - import uws.service.UWSFactory; import uws.service.UWSService; - import uws.service.backup.UWSBackupManager; - import adql.parser.ADQLQueryFactory; import adql.parser.QueryChecker; - import adql.translator.ADQLTranslator; -public interface TAPFactory< R > extends UWSFactory { +public interface TAPFactory extends UWSFactory { public UWSService createUWS() throws TAPException, UWSException; public UWSBackupManager createUWSBackupManager(final UWSService uws) throws TAPException, UWSException; - public ADQLExecutor<R> createADQLExecutor() throws TAPException; + public ADQLExecutor createADQLExecutor() throws TAPException; public ADQLQueryFactory createQueryFactory() throws TAPException; @@ -51,8 +44,8 @@ public interface TAPFactory< R > extends UWSFactory { public ADQLTranslator createADQLTranslator() throws TAPException; - public DBConnection<R> createDBConnection(final String jobID) throws TAPException; + public DBConnection createDBConnection(final String jobID) throws TAPException; - public Uploader createUploader(final DBConnection<R> dbConn) throws TAPException; + public Uploader createUploader(final DBConnection dbConn) throws TAPException; } diff --git a/src/tap/TAPSyncJob.java b/src/tap/TAPSyncJob.java index 0f26d275d67e8512c1d7c73fffcb8d15741a8abe..2a87027c858427046a3ec66a230f4f1da4f265f8 100644 --- a/src/tap/TAPSyncJob.java +++ b/src/tap/TAPSyncJob.java @@ -34,7 +34,7 @@ public class TAPSyncJob { protected static String lastId = null; - protected final ServiceConnection<?> service; + protected final ServiceConnection service; protected final String ID; protected final TAPParameters tapParams; @@ -45,7 +45,7 @@ public class TAPSyncJob { private Date startedAt = null; - public TAPSyncJob(final ServiceConnection<?> service, final TAPParameters params) throws NullPointerException{ + public TAPSyncJob(final ServiceConnection service, final TAPParameters params) throws NullPointerException{ if (params == null) throw new NullPointerException("Missing TAP parameters ! => Impossible to create a synchronous TAP job."); tapParams = params; @@ -95,7 +95,7 @@ public class TAPSyncJob { if (startedAt != null) throw new IllegalStateException("Impossible to restart a synchronous TAP query !"); - ADQLExecutor<?> executor; + ADQLExecutor executor; try{ executor = service.getFactory().createADQLExecutor(); }catch(TAPException e){ @@ -141,14 +141,14 @@ public class TAPSyncJob { public class SyncThread extends Thread { private final String taskDescription; - public final ADQLExecutor<?> executor; + public final ADQLExecutor executor; protected final HttpServletResponse response; protected final String ID; protected final TAPParameters tapParams; protected Throwable exception = null; protected TAPExecutionReport report = null; - public SyncThread(final ADQLExecutor<?> executor, final String ID, final TAPParameters tapParams, final HttpServletResponse response){ + public SyncThread(final ADQLExecutor executor, final String ID, final TAPParameters tapParams, final HttpServletResponse response){ super(JobThread.tg, ID); taskDescription = "Executing the synchronous TAP query " + ID; this.executor = executor; diff --git a/src/tap/backup/DefaultTAPBackupManager.java b/src/tap/backup/DefaultTAPBackupManager.java index 4e4a86f48d84062a03a162b2a9fd8e1bc00c1366..f84e066a0066bd0398526fb6a536daa757033c95 100644 --- a/src/tap/backup/DefaultTAPBackupManager.java +++ b/src/tap/backup/DefaultTAPBackupManager.java @@ -16,7 +16,8 @@ package tap.backup; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ import org.json.JSONException; @@ -30,20 +31,64 @@ import uws.job.UWSJob; import uws.service.UWS; import uws.service.backup.DefaultUWSBackupManager; +/** + * <p>Let backup all TAP asynchronous jobs.</p> + * + * <p><i>note: Basically the saved data are the same, but in addition some execution statistics are also added.</i></p> + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) + * + * @see DefaultUWSBackupManager + */ public class DefaultTAPBackupManager extends DefaultUWSBackupManager { + /** + * Build a default TAP jobs backup manager. + * + * @param uws The UWS containing all the jobs to backup. + * + * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS) + */ public DefaultTAPBackupManager(UWS uws){ super(uws); } + /** + * Build a default TAP jobs backup manager. + * + * @param uws The UWS containing all the jobs to backup. + * @param frequency The backup frequency (in ms ; MUST BE positive and different from 0. + * If negative or 0, the frequency will be automatically set to DEFAULT_FREQUENCY). + * + * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS, long) + */ public DefaultTAPBackupManager(UWS uws, long frequency){ super(uws, frequency); } + /** + * Build a default TAP jobs backup manager. + * + * @param uws The UWS containing all the jobs to backup. + * @param byUser Backup mode. + * + * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS, boolean) + */ public DefaultTAPBackupManager(UWS uws, boolean byUser) throws UWSException{ super(uws, byUser); } + /** + * Build a default TAP jobs backup manager. + * + * @param uws The UWS containing all the jobs to backup. + * @param byUser Backup mode. + * @param frequency The backup frequency (in ms ; MUST BE positive and different from 0. + * If negative or 0, the frequency will be automatically set to DEFAULT_FREQUENCY). + * + * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS, boolean, long) + */ public DefaultTAPBackupManager(UWS uws, boolean byUser, long frequency) throws UWSException{ super(uws, byUser, frequency); } @@ -57,10 +102,8 @@ public class DefaultTAPBackupManager extends DefaultUWSBackupManager { JSONObject jsonExecReport = new JSONObject(); jsonExecReport.put("success", execReport.success); - jsonExecReport.put("sql", execReport.sqlTranslation); jsonExecReport.put("uploadduration", execReport.getUploadDuration()); jsonExecReport.put("parsingduration", execReport.getParsingDuration()); - jsonExecReport.put("translationduration", execReport.getTranslationDuration()); jsonExecReport.put("executionduration", execReport.getExecutionDuration()); jsonExecReport.put("formattingduration", execReport.getFormattingDuration()); jsonExecReport.put("totalduration", execReport.getTotalDuration()); @@ -90,16 +133,12 @@ public class DefaultTAPBackupManager extends DefaultUWSBackupManager { try{ if (key.equalsIgnoreCase("success")) execReport.success = jsonExecReport.getBoolean(key); - else if (key.equalsIgnoreCase("sql")) - execReport.sqlTranslation = jsonExecReport.getString(key); else if (key.equalsIgnoreCase("uploadduration")) execReport.setDuration(ExecutionProgression.UPLOADING, jsonExecReport.getLong(key)); else if (key.equalsIgnoreCase("parsingduration")) execReport.setDuration(ExecutionProgression.PARSING, jsonExecReport.getLong(key)); - else if (key.equalsIgnoreCase("translationduration")) - execReport.setDuration(ExecutionProgression.TRANSLATING, jsonExecReport.getLong(key)); else if (key.equalsIgnoreCase("executionduration")) - execReport.setDuration(ExecutionProgression.EXECUTING_SQL, jsonExecReport.getLong(key)); + execReport.setDuration(ExecutionProgression.EXECUTING_ADQL, jsonExecReport.getLong(key)); else if (key.equalsIgnoreCase("formattingduration")) execReport.setDuration(ExecutionProgression.WRITING_RESULT, jsonExecReport.getLong(key)); else if (key.equalsIgnoreCase("totalduration")) diff --git a/src/tap/db/DBConnection.java b/src/tap/db/DBConnection.java index 695cf14ffde4c236e56b6c99157cb9b5a2aefee2..00a5dbac2e1537d955ff1a51eee3586f1896b122 100644 --- a/src/tap/db/DBConnection.java +++ b/src/tap/db/DBConnection.java @@ -16,47 +16,114 @@ package tap.db; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institute (ARI) */ -import cds.savot.model.SavotTR; +import tap.data.DataReadException; +import tap.data.TableIterator; +import tap.metadata.TAPDM; import tap.metadata.TAPTable; import uws.service.log.UWSLogType; - import adql.query.ADQLQuery; /** - * TODO + * <p>Connection to the "database" (whatever is the type or whether it is linked to a true DBMS connection).</p> * - * @author Grégory Mantelet (CDS) - * @version 06/2012 + * <p>It lets executing ADQL queries and updating the TAP datamodel (with the list of schemas, tables and columns published in TAP, + * or with uploaded tables).</p> * - * @param <R> Result type of the execution of a query (see {@link #executeQuery(String, ADQLQuery)}. + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) */ -public interface DBConnection< R > { +public interface DBConnection { + /** Log type specific to the database activity. + * @see UWSLogType#createCustomLogType(String) */ public final static UWSLogType LOG_TYPE_DB_ACTIVITY = UWSLogType.createCustomLogType("DBActivity"); + /** + * <p>Get any identifier for this connection.</p> + * + * <p><i>note: it is used only for logging purpose.</i></p> + * + * @return ID of this connection. + */ public String getID(); - public void startTransaction() throws DBException; - - public void cancelTransaction() throws DBException; - - public void endTransaction() throws DBException; - - public R executeQuery(final String sqlQuery, final ADQLQuery adqlQuery) throws DBException; - - public void createSchema(final String schemaName) throws DBException; - - public void dropSchema(final String schemaName) throws DBException; - - public void createTable(final TAPTable table) throws DBException; - - public void insertRow(final SavotTR row, final TAPTable table) throws DBException; - - public void dropTable(final TAPTable table) throws DBException; - + /** + * <p>Let executing the given ADQL query.</p> + * + * <p>The result of this query must be formatted as a table, and so must be iterable using a {@link TableIterator}.</p> + * + * <p><i>note: the interpretation of the ADQL query is up to the implementation. In most of the case, it is just needed + * to translate this ADQL query into an SQL query (understandable by the chosen DBMS).</i></p> + * + * @param adqlQuery ADQL query to execute. + * + * @return The table result. + * + * @throws DBException If any errors occurs while executing the query. + */ + public TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException; + + /** + * <p>Add or update the specified TAP_SCHEMA table with the given data.</p> + * + * <p><i><b>Warning:</b> It is expected that the given data SHOULD be the only ones inside the specified table. + * So, the table SHOULD probably be cleared before the insertion of the given data. However, this behavior MAY depend of the + * implementation and more particularly of the way the TAP_SCHEMA is updated.</i></p> + * + * @param tapTableName Name of the TAP_SCHEMA table to add/update. + * @param data Data to use in order to fill the specified table. + * + * @return <i>true</i> if the specified table has been successfully added/updated, <i>false</i> otherwise. + * + * @throws DBException If any error occurs while updating the database. + * @throws DataReadException If any error occurs while reading the given data. + */ + public boolean updateTAPTable(final TAPDM tapTableName, final TableIterator data) throws DBException, DataReadException; + + /** + * Add the defined and given table inside the TAP_UPLOAD schema. + * + * <p><i>note: A table of TAP_UPLOAD MUST be transient and persistent only for the lifetime of the query. + * So, this function should always be used with {@link #dropUploadedTable(String)}, which is called at + * the end of each query execution.</i></p> + * + * @param tableDef Definition of the table to upload (list of all columns and of their type). + * @param data Rows and columns of the table to upload. + * @param maxNbRows Maximum number of rows allowed to be inserted. Beyond this limit, a + * {@link DataReadException} MUST be sent. <i>A negative or a NULL value means "no limit".</i> + * + * @return <i>true</i> if the given table has been successfully added, <i>false</i> otherwise. + * + * @throws DBException If any error occurs while adding the table. + * @throws DataReadException If any error occurs while reading the given data. + */ + public boolean addUploadedTable(final TAPTable tableDef, final TableIterator data, final int maxNbRows) throws DBException, DataReadException; + + /** + * <p>Drop the specified uploaded table from the database. + * More precisely, it means dropping a table from the TAP_UPLOAD schema.</p> + * + * @param tableName Name (in the database) of the uploaded table to drop. + * + * @return <i>true</i> if the specified table has been successfully dropped, <i>false</i> otherwise. + * + * @throws DBException If any error occurs while dropping the specified uploaded table. + */ + public boolean dropUploadedTable(final String tableName) throws DBException; + + /** + * <p>Close the connection (if needed).</p> + * + * <p><i>note: This function is called at the end of a query/job execution, after the result + * has been successfully (or not) fetched. When called, it means the connection is no longer needed + * for the job and so, can be freed (or given back to a pool, for instance).</i></p> + * + * @throws DBException If any error occurs while closing the connection. + */ public void close() throws DBException; } diff --git a/src/tap/error/DefaultTAPErrorWriter.java b/src/tap/error/DefaultTAPErrorWriter.java index 40534f08d768a3a4a23a3ac052808d79c0e3dde3..85236b914e9ded059ab07137abad0249916f8900 100644 --- a/src/tap/error/DefaultTAPErrorWriter.java +++ b/src/tap/error/DefaultTAPErrorWriter.java @@ -16,7 +16,8 @@ package tap.error; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; @@ -46,16 +47,16 @@ import uws.service.log.UWSLog; * write a given error in the best appropriate format. This format is chosen thanks to the "Accept" header of the HTTP request. * If no request is provided or if there is no known format, the HTML format is chosen by default.</p> * - * @author Grégory Mantelet (CDS) - * @version 06/2012 + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) * * @see AbstractServiceErrorWriter */ public class DefaultTAPErrorWriter extends AbstractServiceErrorWriter { - protected final ServiceConnection<?> service; + protected final ServiceConnection service; - public DefaultTAPErrorWriter(final ServiceConnection<?> service){ + public DefaultTAPErrorWriter(final ServiceConnection service){ this.service = service; } diff --git a/src/tap/formatter/JSONFormat.java b/src/tap/formatter/JSONFormat.java index 431ee9861e1586505d40cd87587ec4032bc8a759..974df0546c6108419ef1489066e7d37d842d039f 100644 --- a/src/tap/formatter/JSONFormat.java +++ b/src/tap/formatter/JSONFormat.java @@ -16,7 +16,8 @@ package tap.formatter; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; @@ -26,70 +27,107 @@ import java.io.PrintWriter; import org.json.JSONException; import org.json.JSONWriter; -import cds.savot.writer.SavotWriter; -import adql.db.DBColumn; import tap.ServiceConnection; import tap.TAPException; import tap.TAPExecutionReport; +import tap.data.TableIterator; import tap.metadata.TAPColumn; -import tap.metadata.TAPTypes; +import tap.metadata.TAPType; +import tap.metadata.TAPType.TAPDatatype; +import tap.metadata.VotType; +import adql.db.DBColumn; -public abstract class JSONFormat< R > implements OutputFormat<R> { +/** + * Format any given query (table) result into JSON. + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) + */ +public class JSONFormat implements OutputFormat { /** Indicates whether a format report (start and end date/time) must be printed in the log output. */ private boolean logFormatReport; /** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */ - protected final ServiceConnection<R> service; + protected final ServiceConnection service; - public JSONFormat(final ServiceConnection<R> service){ + /** + * <p>Build a JSON formatter.</p> + * + * <p><i>note: The built formatter will not write a log entry each time a result is written. + * However if you want this behavior you must you {@link #JSONFormat(ServiceConnection, boolean)}.</i></p> + * + * @param service Description of the TAP service. + */ + public JSONFormat(final ServiceConnection service){ this(service, false); } - public JSONFormat(final ServiceConnection<R> service, final boolean logFormatReport){ + /** + * Build a JSON formatter. + * + * @param service Description of the TAP service. + * @param logFormatReport <i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise. + */ + public JSONFormat(final ServiceConnection service, final boolean logFormatReport){ this.service = service; this.logFormatReport = logFormatReport; } + @Override public String getMimeType(){ return "application/json"; } + @Override public String getShortMimeType(){ return "json"; } + @Override public String getDescription(){ return null; } + @Override public String getFileExtension(){ return "json"; } @Override - public void writeResult(R queryResult, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{ + public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{ try{ long start = System.currentTimeMillis(); + // Prepare the output stream for JSON: PrintWriter writer = new PrintWriter(output); JSONWriter out = new JSONWriter(writer); + // { out.object(); + // "metadata": {...} out.key("metadata"); - DBColumn[] columns = writeMetadata(queryResult, out, execReport, thread); + + // Write metadata part: + DBColumn[] columns = writeMetadata(result, out, execReport, thread); writer.flush(); + // "data": {...} out.key("data"); - int nbRows = writeData(queryResult, columns, out, execReport, thread); + // Write the data part: + int nbRows = writeData(result, columns, out, execReport, thread); + + // } out.endObject(); writer.flush(); + // Report stats about the result writing: if (logFormatReport) service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in JSON ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - start) + " ms !"); + }catch(JSONException je){ throw new TAPException("Error while writing a query result in JSON !", je); }catch(IOException ioe){ @@ -97,59 +135,185 @@ public abstract class JSONFormat< R > implements OutputFormat<R> { } } - protected abstract DBColumn[] writeMetadata(R queryResult, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException; + /** + * Write the whole metadata part of the JSON file. + * + * @param result Result to write later (but it contains also metadata that was extracted from the result itself). + * @param out Output stream in which the metadata must be written. + * @param execReport Execution report (which contains the metadata extracted/guessed from the ADQL query). + * @param thread Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need). + * + * @return All the written metadata. + * + * @throws IOException If there is an error while writing something in the output stream. + * @throws InterruptedException If the thread has been interrupted. + * @throws JSONException If there is an error while formatting something in JSON. + * @throws TAPException If any other error occurs. + * + * @see #getValidColMeta(DBColumn, TAPColumn) + */ + protected DBColumn[] writeMetadata(TableIterator result, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{ + out.array(); + + // Get the metadata extracted/guesses from the ADQL query: + DBColumn[] columnsFromQuery = execReport.resultingColumns; + + // Get the metadata extracted from the result: + TAPColumn[] columnsFromResult = result.getMetadata(); + + int indField = 0; + if (columnsFromQuery != null){ + + // For each column: + for(DBColumn field : columnsFromQuery){ + + // Try to build/get appropriate metadata for this field/column: + TAPColumn colFromResult = (columnsFromResult != null && indField < columnsFromResult.length) ? columnsFromResult[indField] : null; + TAPColumn tapCol = getValidColMeta(field, colFromResult); + + // Ensure these metadata are well returned at the end of this function: + columnsFromQuery[indField] = tapCol; + + // Write the field/column metadata in the JSON output: + writeFieldMeta(tapCol, out); + indField++; + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + } + + out.endArray(); + return columnsFromQuery; + } /** - * <p>Formats in a VOTable field and writes the given {@link TAPColumn} in the given Writer.</p> + * Try to get or otherwise to build appropriate metadata using those extracted from the ADQL query and those extracted from the result. + * + * @param typeFromQuery Metadata extracted/guessed from the ADQL query. + * @param typeFromResult Metadata extracted/guessed from the result. * - * <p><i><u>Note:</u> If the VOTable datatype is <code>int</code>, <code>short</code> or <code>long</code> a NULL values is set by adding a node VALUES: <VALUES null="..." /></i></p> + * @return The most appropriate metadata. + */ + protected TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){ + if (typeFromQuery != null && typeFromQuery instanceof TAPColumn) + return (TAPColumn)typeFromQuery; + else if (typeFromResult != null){ + if (typeFromQuery != null) + return (TAPColumn)typeFromResult.copy(typeFromQuery.getDBName(), typeFromQuery.getADQLName(), null); + else + return (TAPColumn)typeFromResult.copy(); + }else + return new TAPColumn((typeFromQuery != null) ? typeFromQuery.getADQLName() : "?", new TAPType(TAPDatatype.VARCHAR), "?"); + } + + /** + * Formats in JSON and writes the given {@link TAPColumn} in the given output. * - * @param col The column metadata to format into a VOTable field. + * @param col The column metadata to format/write in JSON. * @param out The stream in which the formatted column metadata must be written. * * @throws IOException If there is an error while writing the field metadata. + * @throws JSONException If there is an error while formatting something in JSON format. * @throws TAPException If there is any other error (by default: never happen). */ protected void writeFieldMeta(TAPColumn tapCol, JSONWriter out) throws IOException, TAPException, JSONException{ + // { out.object(); + // "name": "..." out.key("name").value(tapCol.getName()); + // "description": "..." (if any) if (tapCol.getDescription() != null && tapCol.getDescription().trim().length() > 0) out.key("description").value(tapCol.getDescription()); - out.key("datatype").value(tapCol.getVotType().datatype); + // "datatype": "..." + VotType votType = tapCol.getVotType(); + out.key("datatype").value(votType.datatype); - int arraysize = tapCol.getVotType().arraysize; - if (arraysize == TAPTypes.STAR_SIZE) - out.key("arraysize").value("*"); - else if (arraysize > 0) - out.key("arraysize").value(arraysize); + // "arraysize": "..." (if any) + if (votType.unlimitedArraysize){ + if (votType.arraysize > 0) + out.key("arraysize").value(votType.arraysize + "*"); + else + out.key("arraysize").value("*"); + }else if (votType.arraysize > 0) + out.key("arraysize").value(votType.arraysize); - if (tapCol.getVotType().xtype != null) - out.key("xtype").value(tapCol.getVotType().xtype); + // "xtype": "..." (if any) + if (votType.xtype != null) + out.key("xtype").value(votType.xtype); + // "unit": "..." (if any) if (tapCol.getUnit() != null && tapCol.getUnit().length() > 0) out.key("unit").value(tapCol.getUnit()); + // "ucd": "..." (if any) if (tapCol.getUcd() != null && tapCol.getUcd().length() > 0) out.key("ucd").value(tapCol.getUcd()); + // "utype": "..." (if any) if (tapCol.getUtype() != null && tapCol.getUtype().length() > 0) out.key("utype").value(tapCol.getUtype()); + // } out.endObject(); } - protected abstract int writeData(R queryResult, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException; + /** + * Write the whole data part of the JSON file. + * + * @param result Result to write. + * @param selectedColumns All columns' metadata. + * @param out Output stream in which the data must be written. + * @param execReport Execution report (which contains the maximum allowed number of records to output). + * @param thread Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need). + * + * @return The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>) + * + * @throws IOException If there is an error while writing something in the output stream. + * @throws InterruptedException If the thread has been interrupted. + * @throws JSONException If there is an error while formatting something in JSON. + * @throws TAPException If any other error occurs. + */ + protected int writeData(TableIterator result, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{ + // [ + out.array(); + + int nbRows = 0; + while(result.nextRow()){ + // Deal with OVERFLOW, if needed: + if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) + break; + + // [ + out.array(); + int indCol = 0; + while(result.hasNextCol()){ + // ... + writeFieldValue(result.nextCol(), selectedColumns[indCol++], out); + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + // ] + out.endArray(); + nbRows++; + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + + // ] + out.endArray(); + return nbRows; + } /** - * <p>Writes the given field value in the given OutputStream.</p> + * <p>Writes the given field value in JSON and into the given output.</p> * - * <p> - * The given value will be encoded as an XML element (see {@link SavotWriter#encodeElement(String)}. - * Besides, if the given value is <code>null</code> and if the column datatype is <code>int</code>, - * <code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written.</p> + * <p><i>note: special numeric values NaN and Inf (double or float) will be written as NULL values.</i></p> * * @param value The value to write. * @param column The corresponding column metadata. diff --git a/src/tap/formatter/OutputFormat.java b/src/tap/formatter/OutputFormat.java index 13fa7c22bf77f3efcd09b80fbda9e092bb407e3a..a13383a1900eb7b6fd62f555b4b149c8091fbee8 100644 --- a/src/tap/formatter/OutputFormat.java +++ b/src/tap/formatter/OutputFormat.java @@ -16,26 +16,24 @@ package tap.formatter; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Astronomisches Rechen Institut (ARI) */ import java.io.OutputStream; import tap.TAPException; import tap.TAPExecutionReport; +import tap.data.TableIterator; /** * Describes an output format and formats a given query result into this format. * - * @author Grégory Mantelet (CDS) - * - * @param <R> The type of raw query result (i.e. {@link java.sql.ResultSet}). + * @author Grégory Mantelet (CDS;ARI) * - * @version 06/2012 - * - * @see VOTableFormat + * @version 2.0 (07/2014) */ -public interface OutputFormat< R > { +public interface OutputFormat { /** * Gets the MIME type corresponding to this format. @@ -68,25 +66,13 @@ public interface OutputFormat< R > { /** * Formats the given query result and writes it in the given output stream. * - * @param queryResult The raw result to format (i.e. a {@link java.sql.ResultSet}). - * @param output The output stream (a ServletOutputStream or a stream on a file) in which the formatted result must be written. - * @param execReport The report of the execution of the TAP query whose the result must be now written. - * @param thread The thread which has asked the result writting. + * @param result The raw (table) result to format. + * @param output The output stream (a ServletOutputStream or a stream on a file) in which the formatted result must be written. + * @param execReport The report of the execution of the TAP query whose the result must be now written. + * @param thread The thread which has asked the result writing. * * @throws TAPException If there is an error while formatting/writing the query result. */ - public void writeResult(final R queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException; - - /* - * Formats the given query result and writes it in some way accessible through the returned {@link Result}. - * - * @param queryResult The raw result to format (i.e. a {@link java.sql.ResultSet}). - * @param job The job which processed the query. - * - * @return The {@link Result} which provides an access to the formatted query result. - * - * @throws TAPException If there is an error while formatting/writing the query result. - * - public Result writeResult(final R queryResult, final TAPJob job) throws TAPException;*/ + public void writeResult(final TableIterator result, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException; } diff --git a/src/tap/formatter/ResultSet2JsonFormatter.java b/src/tap/formatter/ResultSet2JsonFormatter.java deleted file mode 100644 index 2d3561bf4203c92f0876a24fcb8abd1673624fe2..0000000000000000000000000000000000000000 --- a/src/tap/formatter/ResultSet2JsonFormatter.java +++ /dev/null @@ -1,118 +0,0 @@ -package tap.formatter; - -/* - * This file is part of TAPLibrary. - * - * TAPLibrary is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * TAPLibrary is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS) - */ - -import java.io.IOException; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; - -import org.json.JSONException; -import org.json.JSONWriter; - -import tap.ServiceConnection; -import tap.TAPException; -import tap.TAPExecutionReport; -import tap.metadata.TAPColumn; -import tap.metadata.TAPTypes; -import adql.db.DBColumn; - -public class ResultSet2JsonFormatter extends JSONFormat<ResultSet> implements ResultSetFormatter { - - public ResultSet2JsonFormatter(ServiceConnection<ResultSet> service, boolean logFormatReport){ - super(service, logFormatReport); - } - - public ResultSet2JsonFormatter(ServiceConnection<ResultSet> service){ - super(service); - } - - @Override - protected DBColumn[] writeMetadata(ResultSet queryResult, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{ - out.array(); - DBColumn[] selectedColumns = execReport.resultingColumns; - - try{ - ResultSetMetaData meta = queryResult.getMetaData(); - int indField = 1; - if (selectedColumns != null){ - for(DBColumn field : selectedColumns){ - TAPColumn tapCol = null; - try{ - tapCol = (TAPColumn)field; - }catch(ClassCastException ex){ - tapCol = new TAPColumn(field.getADQLName()); - tapCol.setDatatype(meta.getColumnTypeName(indField), TAPTypes.NO_SIZE); - service.getLogger().warning("Unknown DB datatype for the field \"" + tapCol.getName() + "\" ! It is supposed to be \"" + tapCol.getDatatype() + "\" (original value: \"" + meta.getColumnTypeName(indField) + "\")."); - selectedColumns[indField - 1] = tapCol; - } - writeFieldMeta(tapCol, out); - indField++; - - if (thread.isInterrupted()) - throw new InterruptedException(); - } - } - }catch(SQLException e){ - service.getLogger().error("Job N°" + execReport.jobID + " - Impossible to get the metadata of the given ResultSet !", e); - } - - out.endArray(); - return selectedColumns; - } - - @Override - protected int writeData(ResultSet queryResult, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{ - out.array(); - int nbRows = 0; - try{ - int nbColumns = queryResult.getMetaData().getColumnCount(); - while(queryResult.next()){ - if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW ! - break; - - out.array(); - Object value; - for(int i = 1; i <= nbColumns; i++){ - value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]); - writeFieldValue(value, selectedColumns[i - 1], out); - if (thread.isInterrupted()) - throw new InterruptedException(); - } - out.endArray(); - nbRows++; - - if (thread.isInterrupted()) - throw new InterruptedException(); - } - }catch(SQLException se){ - throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", se); - } - - out.endArray(); - return nbRows; - } - - @Override - public Object formatValue(Object value, DBColumn colMeta){ - return value; - } - -} diff --git a/src/tap/formatter/ResultSet2SVFormatter.java b/src/tap/formatter/ResultSet2SVFormatter.java deleted file mode 100644 index 628957071ee609e48f2db64d99bfde961803610d..0000000000000000000000000000000000000000 --- a/src/tap/formatter/ResultSet2SVFormatter.java +++ /dev/null @@ -1,105 +0,0 @@ -package tap.formatter; - -/* - * This file is part of TAPLibrary. - * - * TAPLibrary is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * TAPLibrary is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS) - */ - -import java.io.IOException; -import java.io.PrintWriter; - -import java.sql.ResultSet; -import java.sql.SQLException; - -import adql.db.DBColumn; - -import tap.ServiceConnection; -import tap.TAPException; -import tap.TAPExecutionReport; - -public class ResultSet2SVFormatter extends SVFormat<ResultSet> implements ResultSetFormatter { - - public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, char colSeparator, boolean delimitStrings){ - super(service, colSeparator, delimitStrings); - } - - public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, char colSeparator){ - super(service, colSeparator); - } - - public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, String colSeparator, boolean delimitStrings){ - super(service, colSeparator, delimitStrings); - } - - public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, String colSeparator){ - super(service, colSeparator); - } - - @Override - protected DBColumn[] writeMetadata(ResultSet queryResult, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{ - DBColumn[] selectedColumns = execReport.resultingColumns; - int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length; - if (nbColumns > 0){ - for(int i = 0; i < nbColumns - 1; i++){ - writer.print(selectedColumns[i].getADQLName()); - writer.print(separator); - } - writer.print(selectedColumns[nbColumns - 1].getADQLName()); - writer.println(); - writer.flush(); - } - return selectedColumns; - } - - @Override - protected int writeData(ResultSet queryResult, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{ - int nbRows = 0; - try{ - int nbColumns = queryResult.getMetaData().getColumnCount(); - while(queryResult.next()){ - if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW ! - break; - - Object value; - for(int i = 1; i <= nbColumns; i++){ - value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]); - writeFieldValue(value, selectedColumns[i - 1], writer); - if (i != nbColumns) - writer.print(separator); - if (thread.isInterrupted()) - throw new InterruptedException(); - } - writer.println(); - nbRows++; - - if (thread.isInterrupted()) - throw new InterruptedException(); - } - writer.flush(); - }catch(SQLException se){ - throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", se); - } - - return nbRows; - } - - @Override - public Object formatValue(Object value, DBColumn colMeta){ - return value; - } - -} diff --git a/src/tap/formatter/ResultSet2TextFormatter.java b/src/tap/formatter/ResultSet2TextFormatter.java deleted file mode 100644 index 045bf804ec630856dc7032f3a832fb4de844e1de..0000000000000000000000000000000000000000 --- a/src/tap/formatter/ResultSet2TextFormatter.java +++ /dev/null @@ -1,85 +0,0 @@ -package tap.formatter; - -/* - * This file is part of TAPLibrary. - * - * TAPLibrary is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * TAPLibrary is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS) - */ - -import java.sql.ResultSet; -import java.sql.SQLException; - -import adql.db.DBColumn; - -import cds.util.AsciiTable; - -import tap.ServiceConnection; -import tap.TAPException; -import tap.TAPExecutionReport; - -public class ResultSet2TextFormatter extends TextFormat<ResultSet> implements ResultSetFormatter { - - public ResultSet2TextFormatter(ServiceConnection<ResultSet> service){ - super(service); - } - - @Override - protected String getHeader(ResultSet queryResult, TAPExecutionReport execReport, Thread thread) throws TAPException{ - DBColumn[] selectedColumns = execReport.resultingColumns; - StringBuffer line = new StringBuffer(); - int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length; - if (nbColumns > 0){ - for(int i = 0; i < nbColumns - 1; i++) - line.append(selectedColumns[i].getADQLName()).append('|'); - line.append(selectedColumns[nbColumns - 1].getADQLName()); - } - return line.toString(); - } - - @Override - protected int writeData(ResultSet queryResult, AsciiTable asciiTable, TAPExecutionReport execReport, Thread thread) throws TAPException{ - int nbRows = 0; - try{ - DBColumn[] selectedColumns = execReport.resultingColumns; - int nbColumns = selectedColumns.length; - StringBuffer line = new StringBuffer(); - while(queryResult.next()){ - if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW ! - break; - - line.delete(0, line.length()); - Object value; - for(int i = 1; i <= nbColumns; i++){ - value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]); - writeFieldValue(value, selectedColumns[i - 1], line); - if (i != nbColumns) - line.append('|'); - } - asciiTable.addLine(line.toString()); - nbRows++; - } - }catch(SQLException se){ - throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", se); - } - return nbRows; - } - - @Override - public Object formatValue(Object value, DBColumn colMeta){ - return value; - } - -} diff --git a/src/tap/formatter/ResultSet2VotableFormatter.java b/src/tap/formatter/ResultSet2VotableFormatter.java deleted file mode 100644 index 2452e6fe6b568b5218d661c80bb6f4c326a785a1..0000000000000000000000000000000000000000 --- a/src/tap/formatter/ResultSet2VotableFormatter.java +++ /dev/null @@ -1,125 +0,0 @@ -package tap.formatter; - -/* - * This file is part of TAPLibrary. - * - * TAPLibrary is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * TAPLibrary is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS) - */ - -import java.io.IOException; - -import tap.TAPExecutionReport; -import tap.TAPException; - -import java.io.OutputStream; -import java.io.PrintWriter; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; - -import tap.ServiceConnection; -import tap.metadata.TAPColumn; -import tap.metadata.TAPTypes; -import adql.db.DBColumn; - -/** - * Formats a {@link ResultSet} into a VOTable. - * - * @author Grégory Mantelet (CDS) - * @version 11/2011 - */ -public class ResultSet2VotableFormatter extends VOTableFormat<ResultSet> implements ResultSetFormatter { - - public ResultSet2VotableFormatter(final ServiceConnection<ResultSet> service) throws NullPointerException{ - super(service); - } - - public ResultSet2VotableFormatter(final ServiceConnection<ResultSet> service, final boolean logFormatReport) throws NullPointerException{ - super(service, logFormatReport); - } - - @Override - protected DBColumn[] writeMetadata(final ResultSet queryResult, final PrintWriter output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ - DBColumn[] selectedColumns = execReport.resultingColumns; - try{ - ResultSetMetaData meta = queryResult.getMetaData(); - int indField = 1; - if (selectedColumns != null){ - for(DBColumn field : selectedColumns){ - TAPColumn tapCol = null; - try{ - tapCol = (TAPColumn)field; - }catch(ClassCastException ex){ - tapCol = new TAPColumn(field.getADQLName()); - tapCol.setDatatype(meta.getColumnTypeName(indField), TAPTypes.NO_SIZE); - service.getLogger().warning("Unknown DB datatype for the field \"" + tapCol.getName() + "\" ! It is supposed to be \"" + tapCol.getDatatype() + "\" (original value: \"" + meta.getColumnTypeName(indField) + "\")."); - selectedColumns[indField - 1] = tapCol; - } - writeFieldMeta(tapCol, output); - indField++; - - if (thread.isInterrupted()) - throw new InterruptedException(); - } - } - }catch(SQLException e){ - service.getLogger().error("Job N°" + execReport.jobID + " - Impossible to get the metadata of the given ResultSet !", e); - output.println("<INFO name=\"WARNING\" value=\"MISSING_META\">Error while getting field(s) metadata</INFO>"); - } - return selectedColumns; - } - - @Override - protected int writeData(final ResultSet queryResult, final DBColumn[] selectedColumns, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ - int nbRows = 0; - try{ - output.write("\t\t\t\t<TABLEDATA>\n".getBytes()); - int nbColumns = queryResult.getMetaData().getColumnCount(); - while(queryResult.next()){ - if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) - break; - - output.write("\t\t\t\t\t<TR>\n".getBytes()); - Object value; - for(int i = 1; i <= nbColumns; i++){ - output.write("\t\t\t\t\t\t<TD>".getBytes()); - value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]); - writeFieldValue(value, selectedColumns[i - 1], output); - output.write("</TD>\n".getBytes()); - - if (thread.isInterrupted()) - throw new InterruptedException(); - } - - output.write("\t\t\t\t\t</TR>\n".getBytes()); - nbRows++; - - if (thread.isInterrupted()) - throw new InterruptedException(); - } - output.write("\t\t\t\t</TABLEDATA>\n".getBytes()); - return nbRows; - }catch(SQLException e){ - throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", e); - } - } - - @Override - public Object formatValue(Object value, DBColumn colMeta){ - return value; - } - -} diff --git a/src/tap/formatter/ResultSetFormatter.java b/src/tap/formatter/ResultSetFormatter.java deleted file mode 100644 index bd4608d7cb42c4588ddb1605106c90689e84af66..0000000000000000000000000000000000000000 --- a/src/tap/formatter/ResultSetFormatter.java +++ /dev/null @@ -1,32 +0,0 @@ -package tap.formatter; - -/* - * This file is part of TAPLibrary. - * - * TAPLibrary is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * TAPLibrary is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. - * - * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS) - */ - -import java.sql.ResultSet; - -import tap.TAPException; - -import adql.db.DBColumn; - -public interface ResultSetFormatter extends OutputFormat<ResultSet> { - - public Object formatValue(final Object value, final DBColumn colMeta) throws TAPException; - -} diff --git a/src/tap/formatter/SVFormat.java b/src/tap/formatter/SVFormat.java index 259fff2ef5129a7f1013c079edd7080c4eed363c..81e25c10eb3b189709f5b5d8fa9fd425e8400f38 100644 --- a/src/tap/formatter/SVFormat.java +++ b/src/tap/formatter/SVFormat.java @@ -16,58 +16,107 @@ package tap.formatter; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; -import cds.savot.writer.SavotWriter; -import adql.db.DBColumn; import tap.ServiceConnection; import tap.TAPException; import tap.TAPExecutionReport; +import tap.data.TableIterator; +import adql.db.DBColumn; -public abstract class SVFormat< R > implements OutputFormat<R> { +/** + * Format any given query (table) result into CSV or TSV (or with custom separator). + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) + */ +public class SVFormat implements OutputFormat { /** Indicates whether a format report (start and end date/time) must be printed in the log output. */ private boolean logFormatReport; + /** Column separator for CSV format. */ public static final char COMMA_SEPARATOR = ','; + /** Column separator for sCSV format. */ public static final char SEMI_COLON_SEPARATOR = ';'; + /** Column separator for TSV format. */ public static final char TAB_SEPARATOR = '\t'; - protected final ServiceConnection<R> service; + /** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */ + protected final ServiceConnection service; + /** Column separator to use. */ protected final String separator; + + /** Indicate whether String values must be delimited by double quotes (default) or not. */ protected final boolean delimitStr; - public SVFormat(final ServiceConnection<R> service, char colSeparator){ + /** + * Build a SVFormat (in which String values are delimited by double quotes). + * + * @param service Description of the TAP service. + * @param colSeparator Column separator to use. + */ + public SVFormat(final ServiceConnection service, char colSeparator){ this(service, colSeparator, true); } - public SVFormat(final ServiceConnection<R> service, char colSeparator, boolean delimitStrings){ + /** + * Build a SVFormat. + * + * @param service Description of the TAP service. + * @param colSeparator Column separator to use. + * @param delimitStrings <i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise. + */ + public SVFormat(final ServiceConnection service, char colSeparator, boolean delimitStrings){ this(service, colSeparator, delimitStrings, false); } - public SVFormat(final ServiceConnection<R> service, char colSeparator, boolean delimitStrings, final boolean logFormatReport){ + /** + * Build a SVFormat. + * + * @param service Description of the TAP service. + * @param colSeparator Column separator to use. + * @param delimitStrings <i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise. + * @param logFormatReport <i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise. + */ + public SVFormat(final ServiceConnection service, char colSeparator, boolean delimitStrings, final boolean logFormatReport){ separator = "" + colSeparator; delimitStr = delimitStrings; this.service = service; this.logFormatReport = logFormatReport; } - public SVFormat(final ServiceConnection<R> service, String colSeparator){ + /** + * Build a SVFormat (in which String values are delimited by double quotes). + * + * @param service Description of the TAP service. + * @param colSeparator Column separator to use. + */ + public SVFormat(final ServiceConnection service, String colSeparator){ this(service, colSeparator, true); } - public SVFormat(final ServiceConnection<R> service, String colSeparator, boolean delimitStrings){ + /** + * Build a SVFormat. + * + * @param service Description of the TAP service. + * @param colSeparator Column separator to use. + * @param delimitStrings <i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise. + */ + public SVFormat(final ServiceConnection service, String colSeparator, boolean delimitStrings){ separator = (colSeparator == null) ? ("" + COMMA_SEPARATOR) : colSeparator; delimitStr = delimitStrings; this.service = service; } + @Override public String getMimeType(){ switch(separator.charAt(0)){ case COMMA_SEPARATOR: @@ -80,6 +129,7 @@ public abstract class SVFormat< R > implements OutputFormat<R> { } } + @Override public String getShortMimeType(){ switch(separator.charAt(0)){ case COMMA_SEPARATOR: @@ -92,10 +142,12 @@ public abstract class SVFormat< R > implements OutputFormat<R> { } } + @Override public String getDescription(){ return null; } + @Override public String getFileExtension(){ switch(separator.charAt(0)){ case COMMA_SEPARATOR: @@ -109,20 +161,22 @@ public abstract class SVFormat< R > implements OutputFormat<R> { } @Override - public void writeResult(R queryResult, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{ + public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{ try{ final long startTime = System.currentTimeMillis(); + // Prepare the output stream: final PrintWriter writer = new PrintWriter(output); // Write header: - DBColumn[] columns = writeMetadata(queryResult, writer, execReport, thread); + DBColumn[] columns = writeHeader(result, writer, execReport, thread); // Write data: - int nbRows = writeData(queryResult, columns, writer, execReport, thread); + int nbRows = writeData(result, columns, writer, execReport, thread); writer.flush(); + // Report stats about the result writing: if (logFormatReport) service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in SV[" + delimitStr + "] ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - startTime) + " ms !"); @@ -131,17 +185,96 @@ public abstract class SVFormat< R > implements OutputFormat<R> { } } - protected abstract DBColumn[] writeMetadata(R queryResult, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException; + /** + * Write the whole header (one row whose columns are just the columns' name). + * + * @param result Result to write later (but it contains also metadata that was extracted from the result itself). + * @param writer Output in which the metadata must be written. + * @param execReport Execution report (which contains the metadata extracted/guessed from the ADQL query). + * @param thread Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need). + * + * @return All the written metadata. + * + * @throws IOException If there is an error while writing something in the output. + * @throws InterruptedException If the thread has been interrupted. + * @throws TAPException If any other error occurs. + */ + protected DBColumn[] writeHeader(TableIterator result, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{ + // Get the columns meta: + DBColumn[] selectedColumns = execReport.resultingColumns; + + // If meta are not known, no header will be written: + int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length; + if (nbColumns > 0){ + // Write all columns' name: + for(int i = 0; i < nbColumns - 1; i++){ + writer.print(selectedColumns[i].getADQLName()); + writer.print(separator); + } + writer.print(selectedColumns[nbColumns - 1].getADQLName()); + + // Go to a new line (in order to prepare the data writing): + writer.println(); + writer.flush(); + } + + // Returns the written columns: + return selectedColumns; + } + + /** + * Write all the data rows. + * + * @param result Result to write. + * @param selectedColumns All columns' metadata. + * @param out Output stream in which the data must be written. + * @param execReport Execution report (which contains the maximum allowed number of records to output). + * @param thread Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need). + * + * @return The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>) + * + * @throws IOException If there is an error while writing something in the output stream. + * @throws InterruptedException If the thread has been interrupted. + * @throws TAPException If any other error occurs. + */ + protected int writeData(TableIterator result, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{ + int nbRows = 0; + + while(result.nextRow()){ + // Deal with OVERFLOW, if needed: + if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW ! + break; - protected abstract int writeData(R queryResult, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException; + int indCol = 0; + while(result.hasNextCol()){ + // Write the column value: + writeFieldValue(result.nextCol(), selectedColumns[indCol++], writer); + + // Append the column separator: + if (result.hasNextCol()) + writer.print(separator); + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + writer.println(); + nbRows++; + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + writer.flush(); + + return nbRows; + } /** - * <p>Writes the given field value in the given OutputStream.</p> + * <p>Writes the given field value in the given PrintWriter.</p> * * <p> - * The given value will be encoded as an XML element (see {@link SavotWriter#encodeElement(String)}. - * Besides, if the given value is <code>null</code> and if the column datatype is <code>int</code>, - * <code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written.</p> + * A String value will be delimited if {@link #delimitStr} is true, otherwise this type of value will + * be processed like the other type of values: no delimiter and just transformed into a string. + * </p> * * @param value The value to write. * @param column The corresponding column metadata. diff --git a/src/tap/formatter/TextFormat.java b/src/tap/formatter/TextFormat.java index 9c5c73486ea6a506e802647ad805b9381c70dba8..35aeb21ec42883aaf6daca92d09d8a74219d4daf 100644 --- a/src/tap/formatter/TextFormat.java +++ b/src/tap/formatter/TextFormat.java @@ -16,68 +16,92 @@ package tap.formatter; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ +import java.io.IOException; import java.io.OutputStream; -import adql.db.DBColumn; - -import cds.util.AsciiTable; - import tap.ServiceConnection; - import tap.TAPException; import tap.TAPExecutionReport; +import tap.data.TableIterator; +import adql.db.DBColumn; +import cds.util.AsciiTable; -public abstract class TextFormat< R > implements OutputFormat<R> { +/** + * Format any given query (table) result into a simple table ASCII representation + * (columns' width are adjusted so that all columns are well aligned and of the same width). + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) + */ +public class TextFormat implements OutputFormat { /** Indicates whether a format report (start and end date/time) must be printed in the log output. */ private boolean logFormatReport; - protected final ServiceConnection<R> service; + /** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */ + protected final ServiceConnection service; - public TextFormat(final ServiceConnection<R> service){ + /** + * Build a {@link TextFormat}. + * + * @param service Description of the TAP service. + */ + public TextFormat(final ServiceConnection service){ this(service, false); } - public TextFormat(final ServiceConnection<R> service, final boolean logFormatReport){ + /** + * Build a {@link TextFormat}. + * + * @param service Description of the TAP service. + * @param logFormatReport <i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise. + */ + public TextFormat(final ServiceConnection service, final boolean logFormatReport){ this.service = service; this.logFormatReport = logFormatReport; } + @Override public String getMimeType(){ return "text/plain"; } + @Override public String getShortMimeType(){ return "text"; } + @Override public String getDescription(){ return null; } + @Override public String getFileExtension(){ return "txt"; } @Override - public void writeResult(R queryResult, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{ + public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{ try{ + // Prepare the formatting of the whole output: AsciiTable asciiTable = new AsciiTable('|'); final long startTime = System.currentTimeMillis(); // Write header: - String headerLine = getHeader(queryResult, execReport, thread); + String headerLine = getHeader(result, execReport, thread); asciiTable.addHeaderLine(headerLine); asciiTable.endHeaderLine(); - // Write data: - int nbRows = writeData(queryResult, asciiTable, execReport, thread); + // Write data into the AsciiTable object: + int nbRows = writeData(result, asciiTable, execReport, thread); - // Write all lines in the output stream: + // Finally write the formatted ASCII table (header + data) in the output stream: String[] lines = asciiTable.displayAligned(new int[]{AsciiTable.LEFT}); for(String l : lines){ output.write(l.getBytes()); @@ -85,6 +109,7 @@ public abstract class TextFormat< R > implements OutputFormat<R> { } output.flush(); + // Report stats about the result writing: if (logFormatReport) service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in text ; " + nbRows + " rows ; " + ((execReport != null && execReport.resultingColumns != null) ? "?" : execReport.resultingColumns.length) + " columns) in " + (System.currentTimeMillis() - startTime) + " ms !"); @@ -93,10 +118,95 @@ public abstract class TextFormat< R > implements OutputFormat<R> { } } - protected abstract String getHeader(final R queryResult, final TAPExecutionReport execReport, final Thread thread) throws TAPException; + /** + * Get the whole header (one row whose columns are just the columns' name). + * + * @param result Result to write later (but it contains also metadata that was extracted from the result itself). + * @param writer Output in which the metadata must be written. + * @param execReport Execution report (which contains the metadata extracted/guessed from the ADQL query). + * @param thread Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need). + * + * @return All the written metadata. + * + * @throws TAPException If any other error occurs. + */ + protected String getHeader(final TableIterator result, final TAPExecutionReport execReport, final Thread thread) throws TAPException{ + // Get the columns meta: + DBColumn[] selectedColumns = execReport.resultingColumns; + + StringBuffer line = new StringBuffer(); + + // If meta are not known, no header will be written: + int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length; + if (nbColumns > 0){ + + // Write all columns' name: + for(int i = 0; i < nbColumns - 1; i++) + line.append(selectedColumns[i].getADQLName()).append('|'); + line.append(selectedColumns[nbColumns - 1].getADQLName()); + } + + // Return the header line: + return line.toString(); + } - protected abstract int writeData(final R queryResult, final AsciiTable asciiTable, final TAPExecutionReport execReport, final Thread thread) throws TAPException; + /** + * Write all the data rows into the given {@link AsciiTable} object. + * + * @param result Result to write. + * @param asciiTable Output in which the rows (as string) must be written. + * @param execReport Execution report (which contains the maximum allowed number of records to output). + * @param thread Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need). + * + * @return The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>) + * + * @throws IOException If there is an error while writing something in the output stream. + * @throws InterruptedException If the thread has been interrupted. + * @throws TAPException If any other error occurs. + */ + protected int writeData(final TableIterator queryResult, final AsciiTable asciiTable, final TAPExecutionReport execReport, final Thread thread) throws TAPException{ + int nbRows = 0; + + // Get the list of columns: + DBColumn[] selectedColumns = execReport.resultingColumns; + int nbColumns = selectedColumns.length; + + StringBuffer line = new StringBuffer(); + while(queryResult.nextRow()){ + // Deal with OVERFLOW, if needed: + if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) + break; + + // Clear the line buffer: + line.delete(0, line.length()); + + int indCol = 0; + while(queryResult.hasNextCol()){ + + // Write the column value: + writeFieldValue(queryResult.nextCol(), selectedColumns[indCol++], line); + + // Write the column separator (if needed): + if (indCol != nbColumns) + line.append('|'); + } + + // Append the line/row in the ASCII table: + asciiTable.addLine(line.toString()); + + nbRows++; + } + + return nbRows; + } + /** + * Writes the given field value in the given buffer. + * + * @param value The value to write. + * @param tapCol The corresponding column metadata. + * @param line The buffer in which the field value must be written. + */ protected void writeFieldValue(final Object value, final DBColumn tapCol, final StringBuffer line){ Object obj = value; if (obj != null) diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java index fa3220c1d6fc3528157f166afe14be36bd8513bf..04ca76a0508114add3e6b4d5cabcfe6e4722f78b 100644 --- a/src/tap/formatter/VOTableFormat.java +++ b/src/tap/formatter/VOTableFormat.java @@ -16,58 +16,45 @@ package tap.formatter; * 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; - -import tap.TAPExecutionReport; -import tap.TAPJob; -import tap.TAPException; -import uws.job.Result; - import java.io.OutputStream; import java.io.PrintWriter; -import cds.savot.writer.SavotWriter; import tap.ServiceConnection; +import tap.TAPException; +import tap.TAPExecutionReport; +import tap.TAPJob; +import tap.data.TableIterator; import tap.metadata.TAPColumn; +import tap.metadata.TAPType; +import tap.metadata.TAPType.TAPDatatype; import tap.metadata.VotType; +import tap.metadata.VotType.VotDatatype; import adql.db.DBColumn; +import cds.savot.writer.SavotWriter; /** - * <p>Formats the given type of query result in VOTable.</p> - * <p> - * This abstract class is only able to format the skeleton of the VOTable. - * However, it also provides useful methods to format field metadata and field value (including NULL values). - * </p> + * <p>Format any given query (table) result into VOTable.</p> * <p> * Attributes of the VOTable node are by default set by this class but can be overridden if necessary thanks to the corresponding class attributes: * {@link #votTableVersion}, {@link #xmlnsXsi}, {@link #xsiNoNamespaceSchemaLocation}, {@link #xsiSchemaLocation} and * {@link #xmlns}. * </p> - * <p> - * When overridding this class, you must implement {@link #writeMetadata(Object, PrintWriter, TAPJob)} and - * {@link #writeData(Object, DBColumn[], OutputStream, TAPJob)}. - * Both are called by {@link #writeResult(Object, OutputStream, TAPJob)}. Finally you will also have to implement - * {@link #writeResult(Object, TAPJob)}, which must format the given result into a VOTable saved in some way accessible - * through the returned {@link Result}. - * </p> - * - * @author Grégory Mantelet (CDS) - * @version 06/2012 - * - * @param <R> Type of the result to format in VOTable (i.e. {@link java.sql.ResultSet}). * - * @see ResultSet2VotableFormatter + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) */ -public abstract class VOTableFormat< R > implements OutputFormat<R> { +public class VOTableFormat implements OutputFormat { /** Indicates whether a format report (start and end date/time) must be printed in the log output. */ private boolean logFormatReport; /** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */ - protected final ServiceConnection<R> service; + protected final ServiceConnection service; protected String votTableVersion = "1.2"; protected String xmlnsXsi = "http://www.w3.org/2001/XMLSchema-instance"; @@ -84,7 +71,7 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { * * @see #VOTableFormat(ServiceConnection, boolean) */ - public VOTableFormat(final ServiceConnection<R> service) throws NullPointerException{ + public VOTableFormat(final ServiceConnection service) throws NullPointerException{ this(service, false); } @@ -96,25 +83,29 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { * * @throws NullPointerException If the given service connection is <code>null</code>. */ - public VOTableFormat(final ServiceConnection<R> service, final boolean logFormatReport) throws NullPointerException{ + public VOTableFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{ if (service == null) throw new NullPointerException("The given service connection is NULL !"); this.service = service; this.logFormatReport = logFormatReport; } + @Override public final String getMimeType(){ return "text/xml"; } + @Override public final String getShortMimeType(){ return "votable"; } + @Override public String getDescription(){ return null; } + @Override public String getFileExtension(){ return "xml"; } @@ -135,7 +126,8 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { * * @see tap.formatter.OutputFormat#writeResult(Object, OutputStream, TAPExecutionReport) */ - public final void writeResult(final R queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{ + @Override + public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{ try{ long start = System.currentTimeMillis(); @@ -205,10 +197,10 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { * <p>Writes fields' metadata of the given query result in the given Writer.</p> * <p><b><u>Important:</u> To write write metadata of a given field you can use {@link #writeFieldMeta(TAPColumn, PrintWriter)}.</b></p> * - * @param queryResult The query result from whose fields' metadata must be written. + * @param result The query result from whose fields' metadata must be written. * @param output Writer in which fields' metadata must be written. * @param execReport The report of the query execution. - * @param thread The thread which asked for the result writting. + * @param thread The thread which asked for the result writing. * * @return Extracted field's metadata. * @@ -216,7 +208,58 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { * @throws TAPException If there is any other error. * @throws InterruptedException If the given thread has been interrupted. */ - protected abstract DBColumn[] writeMetadata(final R queryResult, final PrintWriter output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException; + protected DBColumn[] writeMetadata(final TableIterator result, final PrintWriter output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ + // Get the metadata extracted/guesses from the ADQL query: + DBColumn[] columnsFromQuery = execReport.resultingColumns; + + // Get the metadata extracted from the result: + TAPColumn[] columnsFromResult = result.getMetadata(); + + int indField = 0; + if (columnsFromQuery != null){ + + // For each column: + for(DBColumn field : columnsFromQuery){ + + // Try to build/get appropriate metadata for this field/column: + TAPColumn colFromResult = (columnsFromResult != null && indField < columnsFromResult.length) ? columnsFromResult[indField] : null; + TAPColumn tapCol = getValidColMeta(field, colFromResult); + + // Ensure these metadata are well returned at the end of this function: + columnsFromQuery[indField] = tapCol; + + // Write the field/column metadata in the JSON output: + writeFieldMeta(tapCol, output); + indField++; + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + }else + output.println("<INFO name=\"WARNING\" value=\"MISSING_META\">Error while getting field(s) metadata</INFO>"); + + return columnsFromQuery; + } + + /** + * Try to get or otherwise to build appropriate metadata using those extracted from the ADQL query and those extracted from the result. + * + * @param typeFromQuery Metadata extracted/guessed from the ADQL query. + * @param typeFromResult Metadata extracted/guessed from the result. + * + * @return The most appropriate metadata. + */ + protected TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){ + if (typeFromQuery != null && typeFromQuery instanceof TAPColumn) + return (TAPColumn)typeFromQuery; + else if (typeFromResult != null){ + if (typeFromQuery != null) + return (TAPColumn)typeFromResult.copy(typeFromQuery.getDBName(), typeFromQuery.getADQLName(), null); + else + return (TAPColumn)typeFromResult.copy(); + }else + return new TAPColumn((typeFromQuery != null) ? typeFromQuery.getADQLName() : "?", new TAPType(TAPDatatype.VARCHAR), "?"); + } /** * <p>Formats in a VOTable field and writes the given {@link TAPColumn} in the given Writer.</p> @@ -232,34 +275,49 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { protected void writeFieldMeta(TAPColumn col, PrintWriter out) throws IOException, TAPException{ StringBuffer fieldline = new StringBuffer("\t\t\t"); + // <FIELD ID="..." fieldline.append("<FIELD ID=").append('"').append(SavotWriter.encodeAttribute(col.getADQLName())).append('"'); + + // name="..." fieldline.append(" name=").append('"').append(SavotWriter.encodeAttribute(col.getADQLName())).append('"'); + // datatype="..." VotType type = col.getVotType(); - String nullVal = getNullValue(type.datatype), description = null; - + String nullVal = getNullValue(type.datatype); fieldline.append(' ').append(type.toString()); + // ucd="..." (if any) if (col.getUcd() != null && col.getUcd().length() > 0) fieldline.append(" ucd=").append('"').append(SavotWriter.encodeAttribute(col.getUcd())).append('"'); + // utype="..." (if any) if (col.getUtype() != null && col.getUtype().length() > 0) fieldline.append(" utype=").append('"').append(SavotWriter.encodeAttribute(col.getUtype())).append('"'); + // unit="..." (if any) if (col.getUnit() != null && col.getUnit().length() > 0) fieldline.append(" unit=").append('"').append(SavotWriter.encodeAttribute(col.getUnit())).append('"'); + // Get the description (or NULL if none is provided): + String description = null; if (col.getDescription() != null && !col.getDescription().trim().isEmpty()) description = col.getDescription().trim(); else description = null; + // Declares NULL values and write the description: if (nullVal != null || description != null){ fieldline.append(">\n"); + + // <VALUES null="..." /> (if needed) if (nullVal != null) fieldline.append("<VALUES null=\"" + nullVal + "\" />\n"); + + // <DESCRIPTION>...</DESCRIPTION> (if any) if (description != null) fieldline.append("<DESCRIPTION>").append(SavotWriter.encodeElement(description)).append("</DESCRIPTION>\n"); + + // </FIELD> fieldline.append("</FIELD>"); out.println(fieldline); }else{ @@ -269,22 +327,57 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { } /** - * <p>Writes the data of the given query result in the given OutputStream.</p> - * <p><b><u>Important:</u> To write a field value you can use {@link #writeFieldValue(Object, DBColumn, OutputStream)}.</b></p> + * Write the whole data part (TABLEDATA) of the VOTable file. * - * @param queryResult The query result which contains the data to write. + * @param result The query result which contains the data to write. * @param selectedColumns The columns selected by the query. * @param output The stream in which the data must be written. - * @param execReport The report of the query execution. - * @param thread The thread which asked for the result writting. + * @param execReport The report of the query execution (which contains the maximum allowed number of records to output). + * @param thread The thread which asked for the result writing. * - * @return The number of written rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>) + * @return The number of written rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>) * * @throws IOException If there is an error while writing the data in the given stream. - * @throws TAPException If there is any other error. * @throws InterruptedException If the given thread has been interrupted. + * @throws TAPException If there is any other error. */ - protected abstract int writeData(final R queryResult, final DBColumn[] selectedColumns, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException; + protected int writeData(final TableIterator result, final DBColumn[] selectedColumns, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ + // <TABLEDATA> + output.write("\t\t\t\t<TABLEDATA>\n".getBytes()); + + int nbRows = 0; + while(result.nextRow()){ + // Deal with OVERFLOW, if needed: + if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) + break; + + // <TR> + output.write("\t\t\t\t\t<TR>\n".getBytes()); + int indCol = 0; + while(result.hasNextCol()){ + // <TD> + output.write("\t\t\t\t\t\t<TD>".getBytes()); + // ... + writeFieldValue(result.nextCol(), selectedColumns[indCol++], output); + // </TD> + output.write("</TD>\n".getBytes()); + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + + // </TR> + output.write("\t\t\t\t\t</TR>\n".getBytes()); + nbRows++; + + if (thread.isInterrupted()) + throw new InterruptedException(); + } + + // </TABLEDATA> + output.write("\t\t\t\t</TABLEDATA>\n".getBytes()); + return nbRows; + } /** * <p>Writes the given field value in the given OutputStream.</p> @@ -292,7 +385,8 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { * <p> * The given value will be encoded as an XML element (see {@link SavotWriter#encodeElement(String)}. * Besides, if the given value is <code>null</code> and if the column datatype is <code>int</code>, - * <code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written.</p> + * <code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written. + * </p> * * @param value The value to write. * @param column The corresponding column metadata. @@ -322,19 +416,19 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> { * * @return The corresponding NULL value, or <code>null</code> if there is none. */ - public static final String getNullValue(String datatype){ + public static final String getNullValue(VotDatatype datatype){ if (datatype == null) return null; - datatype = datatype.trim().toLowerCase(); - - if (datatype.equals("short")) - return "" + Short.MIN_VALUE; - else if (datatype.equals("int")) - return "" + Integer.MIN_VALUE; - else if (datatype.equals("long")) - return "" + Long.MIN_VALUE; - else - return null; + switch(datatype){ + case SHORT: + return "" + Short.MIN_VALUE; + case INT: + return "" + Integer.MIN_VALUE; + case LONG: + return "" + Long.MIN_VALUE; + default: + return null; + } } } diff --git a/src/tap/log/DefaultTAPLog.java b/src/tap/log/DefaultTAPLog.java index 62b6971bcef008cfa401467d91059598b8cd2be1..10b22de68eb8a52e1bc17faf163a9c5f6a4bf767 100644 --- a/src/tap/log/DefaultTAPLog.java +++ b/src/tap/log/DefaultTAPLog.java @@ -16,7 +16,8 @@ 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ import java.io.OutputStream; @@ -24,19 +25,16 @@ import java.io.PrintWriter; import tap.TAPExecutionReport; import tap.db.DBConnection; - import tap.file.TAPFileManager; - import tap.metadata.TAPMetadata; import tap.metadata.TAPTable; - import uws.service.log.DefaultUWSLog; /** * Default implementation of the {@link TAPLog} interface which lets logging any message about a TAP service. * - * @author Grégory Mantelet (CDS) - * @version 06/2012 + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) * * @see DefaultUWSLog */ @@ -54,6 +52,7 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog { super(writer); } + @Override public void queryFinished(final TAPExecutionReport report){ StringBuffer buffer = new StringBuffer("QUERY END FOR " + report.jobID + ""); buffer.append(" - success ? ").append(report.success); @@ -61,7 +60,6 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog { buffer.append(" - total duration = ").append(report.getTotalDuration()).append("ms"); buffer.append(" => upload=").append(report.getUploadDuration()).append("ms"); buffer.append(", parsing=").append(report.getParsingDuration()).append("ms"); - buffer.append(", translating=").append(report.getTranslationDuration()).append("ms"); buffer.append(", execution=").append(report.getExecutionDuration()).append("ms"); buffer.append(", formatting[").append(report.parameters.getFormat()).append("]=").append(report.getFormattingDuration()).append("ms"); info(buffer.toString()); @@ -76,10 +74,12 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog { log(DBConnection.LOG_TYPE_DB_ACTIVITY, ((message == null) ? null : (msgType + message)), t); } + @Override public void dbInfo(final String message){ dbActivity(message); } + @Override public void dbError(final String message, final Throwable t){ dbActivity(message, t); } @@ -95,71 +95,26 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog { } @Override - public void connectionOpened(DBConnection<?> connection, String dbName){ - //dbActivity("A connection has been opened to the database \""+dbName+"\" !"); - } - - @Override - public void connectionClosed(DBConnection<?> connection){ + public void connectionClosed(DBConnection connection){ //dbActivity("A database connection has been closed !"); } - @Override - public void transactionStarted(final DBConnection<?> connection){ - //dbActivity("A transaction has been started !"); - } - - @Override - public void transactionCancelled(final DBConnection<?> connection){ - //dbActivity("A transaction has been cancelled !"); - } - - @Override - public void transactionEnded(final DBConnection<?> connection){ - //dbActivity("A transaction has been ended/commited !"); - } - - @Override - public void schemaCreated(final DBConnection<?> connection, String schema){ - dbActivity("CREATE SCHEMA \"" + schema + "\"\t" + connection.getID()); - } - - @Override - public void schemaDropped(final DBConnection<?> connection, String schema){ - dbActivity("DROP SCHEMA \"" + schema + "\"\t" + connection.getID()); - } - protected final String getFullDBName(final TAPTable table){ return (table.getSchema() != null) ? (table.getSchema().getDBName() + ".") : ""; } @Override - public void tableCreated(final DBConnection<?> connection, TAPTable table){ - dbActivity("CREATE TABLE \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID()); - } - - @Override - public void tableDropped(final DBConnection<?> connection, TAPTable table){ - dbActivity("DROP TABLE \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID()); - } - - @Override - public void rowsInserted(final DBConnection<?> connection, TAPTable table, int nbInsertedRows){ - dbActivity("INSERT ROWS (" + ((nbInsertedRows > 0) ? nbInsertedRows : "???") + ") into \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID()); - } - - @Override - public void sqlQueryExecuting(final DBConnection<?> connection, String sql){ + public void sqlQueryExecuting(final DBConnection connection, String sql){ dbActivity("EXECUTING SQL QUERY \t" + connection.getID() + "\n" + ((sql == null) ? "???" : sql.replaceAll("\n", " ").replaceAll("\t", " ").replaceAll("\r", ""))); } @Override - public void sqlQueryError(final DBConnection<?> connection, String sql, Throwable t){ + public void sqlQueryError(final DBConnection connection, String sql, Throwable t){ dbActivity("EXECUTION ERROR\t" + connection.getID(), t); } @Override - public void sqlQueryExecuted(final DBConnection<?> connection, String sql){ + public void sqlQueryExecuted(final DBConnection connection, String sql){ dbActivity("SUCCESSFULL END OF EXECUTION\t" + connection.getID()); } diff --git a/src/tap/log/TAPLog.java b/src/tap/log/TAPLog.java index 04439aa2297c4bdee7bb9f22e07f8aa8de2f5f86..6d15c8fd47adc69dde6fd29efde187c8ddc21f24 100644 --- a/src/tap/log/TAPLog.java +++ b/src/tap/log/TAPLog.java @@ -16,22 +16,20 @@ 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Astronomisches Rechen Institut (ARI) */ import tap.TAPExecutionReport; import tap.db.DBConnection; - import tap.metadata.TAPMetadata; -import tap.metadata.TAPTable; - import uws.service.log.UWSLog; /** * Lets logging any kind of message about a TAP service. * - * @author Grégory Mantelet (CDS) - * @version 06/2012 + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) */ public interface TAPLog extends UWSLog { @@ -45,30 +43,12 @@ public interface TAPLog extends UWSLog { public void tapMetadataLoaded(final TAPMetadata metadata); - public void connectionOpened(final DBConnection<?> connection, final String dbName); - - public void connectionClosed(final DBConnection<?> connection); - - public void transactionStarted(final DBConnection<?> connection); - - public void transactionCancelled(final DBConnection<?> connection); - - public void transactionEnded(final DBConnection<?> connection); - - public void schemaCreated(final DBConnection<?> connection, final String schema); - - public void schemaDropped(final DBConnection<?> connection, final String schema); - - public void tableCreated(final DBConnection<?> connection, final TAPTable table); - - public void tableDropped(final DBConnection<?> connection, final TAPTable table); - - public void rowsInserted(final DBConnection<?> connection, final TAPTable table, final int nbInsertedRows); + public void connectionClosed(final DBConnection connection); - public void sqlQueryExecuting(final DBConnection<?> connection, final String sql); + public void sqlQueryExecuting(final DBConnection connection, final String sql); - public void sqlQueryError(final DBConnection<?> connection, final String sql, final Throwable t); + public void sqlQueryError(final DBConnection connection, final String sql, final Throwable t); - public void sqlQueryExecuted(final DBConnection<?> connection, final String sql); + public void sqlQueryExecuted(final DBConnection connection, final String sql); } diff --git a/src/tap/metadata/TAPDM.java b/src/tap/metadata/TAPDM.java new file mode 100644 index 0000000000000000000000000000000000000000..5a0548face612fceedaf6ccd598cdcd42e74053f --- /dev/null +++ b/src/tap/metadata/TAPDM.java @@ -0,0 +1,52 @@ +package tap.metadata; + +/* + * This file is part of TAPLibrary. + * + * TAPLibrary is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TAPLibrary is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright 2014 - Astronomisches Rechen Institute (ARI) + */ + +/** + * Enumeration of all schemas and tables of the TAP datamodel (and particularly of TAP_SCHEMA). + * + * @author Grégory Mantelet (ARI) + * @version 2.0 (07/2014) + * @since 2.0 + */ +public enum TAPDM{ + TAPSCHEMA("TAP_SCHEMA"), SCHEMAS("schemas"), TABLES("tables"), COLUMNS("columns"), FOREIGN_KEYS("foreign_keys"), UPLOADSCHEMA("TAP_UPLOAD"); + + /** Real name of the schema/table. */ + private final String label; + + private TAPDM(final String name){ + this.label = name; + } + + /** + * Get the real name of the schema/table of the TAP datamodel. + * + * @return Real name of the schema/table. + */ + public String getLabel(){ + return label; + } + + @Override + public String toString(){ + return label; + } +} diff --git a/src/tap/metadata/VotType.java b/src/tap/metadata/VotType.java index c72db5c48b43a3a88c1e2290c3aa374104e7932d..000f5f81add035c5f1f6a8acce2c22c719726a30 100644 --- a/src/tap/metadata/VotType.java +++ b/src/tap/metadata/VotType.java @@ -31,18 +31,18 @@ import cds.savot.writer.SavotWriter; * </ul> * * @author Grégory Mantelet (CDS;ARI) - * @version 06/2014 + * @version 2.0 (07/2014) */ public final class VotType { /** * All possible values for a VOTable datatype (i.e. boolean, short, char, ...). * * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de - * @version 2.0 (06/2014) + * @version 2.0 (07/2014) * @since 2.0 */ public static enum VotDatatype{ - BOOLEAN("boolean"), SHORT("short"), INT("int"), LONG("long"), FLOAT("float"), DOUBLE("double"), CHAR("char"), UNSIGNED_BYTE("unsignedByte"); + BOOLEAN("boolean"), BIT("bit"), UNSIGNED_BYTE("unsignedByte"), SHORT("short"), INT("int"), LONG("long"), CHAR("char"), UNICODE_CHAR("unicodeChar"), FLOAT("float"), DOUBLE("double"), FLOAT_COMPLEX("floatComplex"), DOUBLE_COMPLEX("doubleComplex"); private final String strExpr; diff --git a/src/tap/parameters/FormatController.java b/src/tap/parameters/FormatController.java index 46f2d2fea72773f756ff2bee96075036eccb7060..526536fece92570323fcc60e959e2450d8120a16 100644 --- a/src/tap/parameters/FormatController.java +++ b/src/tap/parameters/FormatController.java @@ -28,12 +28,12 @@ import uws.UWSException; import uws.UWSExceptionFactory; import uws.job.parameters.InputParamController; -public class FormatController< R > implements InputParamController { +public class FormatController implements InputParamController { - protected final ServiceConnection<R> service; + protected final ServiceConnection service; protected boolean allowModification = true; - public FormatController(final ServiceConnection<R> service){ + public FormatController(final ServiceConnection service){ this.service = service; } @@ -70,10 +70,10 @@ public class FormatController< R > implements InputParamController { } public final String getAllowedFormats(){ - Iterator<OutputFormat<R>> itFormats = service.getOutputFormats(); + Iterator<OutputFormat> itFormats = service.getOutputFormats(); StringBuffer allowedFormats = new StringBuffer(); int i = 0; - OutputFormat<R> formatter; + OutputFormat formatter; while(itFormats.hasNext()){ formatter = itFormats.next(); allowedFormats.append((i == 0) ? "" : ", ").append(formatter.getMimeType()); diff --git a/src/tap/parameters/MaxRecController.java b/src/tap/parameters/MaxRecController.java index 08f29b6c605ccce2f2dafb2e3636bda05b630261..30851bc4d51456f1661b0f9a39a1512b362f8606 100644 --- a/src/tap/parameters/MaxRecController.java +++ b/src/tap/parameters/MaxRecController.java @@ -39,12 +39,12 @@ import uws.job.parameters.InputParamController; */ public class MaxRecController implements InputParamController { - protected final ServiceConnection<?> service; + protected final ServiceConnection service; /** Indicates whether the output limit of jobs can be modified. */ protected boolean allowModification = true; - public MaxRecController(final ServiceConnection<?> service){ + public MaxRecController(final ServiceConnection service){ this.service = service; allowModification(allowModification); } diff --git a/src/tap/parameters/TAPDestructionTimeController.java b/src/tap/parameters/TAPDestructionTimeController.java index 33444bdcf35ecae0b0515686f98552e69478f774..a3c0ca644fdd73c622c704525b9779adc12cabbd 100644 --- a/src/tap/parameters/TAPDestructionTimeController.java +++ b/src/tap/parameters/TAPDestructionTimeController.java @@ -25,20 +25,18 @@ import java.util.Date; import tap.ServiceConnection; import tap.TAPJob; - import uws.UWSException; import uws.UWSExceptionFactory; - import uws.job.UWSJob; -import uws.job.parameters.InputParamController; import uws.job.parameters.DestructionTimeController.DateField; +import uws.job.parameters.InputParamController; public class TAPDestructionTimeController implements InputParamController { - protected final ServiceConnection<?> service; + protected final ServiceConnection service; protected boolean allowModification = true; - public TAPDestructionTimeController(final ServiceConnection<?> service){ + public TAPDestructionTimeController(final ServiceConnection service){ this.service = service; } diff --git a/src/tap/parameters/TAPExecutionDurationController.java b/src/tap/parameters/TAPExecutionDurationController.java index 68f5797e6bb8860f3917d749ce4083da49fb0388..2187beda9b0a7a9fba3a13413d86a378a9d68b28 100644 --- a/src/tap/parameters/TAPExecutionDurationController.java +++ b/src/tap/parameters/TAPExecutionDurationController.java @@ -21,18 +21,16 @@ package tap.parameters; import tap.ServiceConnection; import tap.TAPJob; - import uws.UWSException; import uws.UWSExceptionFactory; - import uws.job.parameters.InputParamController; public class TAPExecutionDurationController implements InputParamController { - protected final ServiceConnection<?> service; + protected final ServiceConnection service; protected boolean allowModification = true; - public TAPExecutionDurationController(final ServiceConnection<?> service){ + public TAPExecutionDurationController(final ServiceConnection service){ this.service = service; } diff --git a/src/tap/parameters/TAPParameters.java b/src/tap/parameters/TAPParameters.java index f45c833ff840e87d28ac3c7a5e9a9a93f16a473b..697e37a3d7ae11687fbca0631bb76ab3e1a43ec0 100644 --- a/src/tap/parameters/TAPParameters.java +++ b/src/tap/parameters/TAPParameters.java @@ -21,7 +21,6 @@ package tap.parameters; import java.io.File; import java.io.IOException; - import java.util.Collection; import java.util.Date; import java.util.Enumeration; @@ -30,21 +29,18 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; -import com.oreilly.servlet.MultipartRequest; -import com.oreilly.servlet.multipart.FileRenamePolicy; - import tap.ServiceConnection; import tap.TAPException; import tap.TAPJob; - import tap.upload.TableLoader; - import uws.UWSException; - import uws.job.parameters.InputParamController; import uws.job.parameters.StringParamController; import uws.job.parameters.UWSParameters; +import com.oreilly.servlet.MultipartRequest; +import com.oreilly.servlet.multipart.FileRenamePolicy; + /** * This class describes all defined parameters of a TAP request. * @@ -65,21 +61,21 @@ public class TAPParameters extends UWSParameters { protected TableLoader[] tablesToUpload = null; @SuppressWarnings({"unchecked"}) - public TAPParameters(final ServiceConnection<?> service){ + public TAPParameters(final ServiceConnection service){ this(service, (Collection)null, null); } - public TAPParameters(final ServiceConnection<?> service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers){ + public TAPParameters(final ServiceConnection service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers){ super(expectedAdditionalParams, inputParamControllers); initDefaultTAPControllers(service); } - public TAPParameters(final HttpServletRequest request, final ServiceConnection<?> service) throws UWSException, TAPException{ + public TAPParameters(final HttpServletRequest request, final ServiceConnection service) throws UWSException, TAPException{ this(request, service, null, null); } @SuppressWarnings("unchecked") - public TAPParameters(final HttpServletRequest request, final ServiceConnection<?> service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{ + public TAPParameters(final HttpServletRequest request, final ServiceConnection service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{ this(service, expectedAdditionalParams, inputParamControllers); MultipartRequest multipart = null; @@ -128,11 +124,11 @@ public class TAPParameters extends UWSParameters { tablesToUpload = buildLoaders(uploadParam, multipart); } - public TAPParameters(final ServiceConnection<?> service, final Map<String,Object> params) throws UWSException, TAPException{ + public TAPParameters(final ServiceConnection service, final Map<String,Object> params) throws UWSException, TAPException{ this(service, params, null, null); } - public TAPParameters(final ServiceConnection<?> service, final Map<String,Object> params, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{ + public TAPParameters(final ServiceConnection service, final Map<String,Object> params, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{ super(params, expectedAdditionalParams, inputParamControllers); initDefaultTAPControllers(service); } @@ -142,7 +138,7 @@ public class TAPParameters extends UWSParameters { return new HashMap<String,InputParamController>(10); } - protected < R > void initDefaultTAPControllers(final ServiceConnection<R> service){ + protected < R > void initDefaultTAPControllers(final ServiceConnection service){ if (!mapParamControllers.containsKey(TAPJob.PARAM_EXECUTION_DURATION)) mapParamControllers.put(TAPJob.PARAM_EXECUTION_DURATION, new TAPExecutionDurationController(service)); @@ -165,7 +161,7 @@ public class TAPParameters extends UWSParameters { mapParamControllers.put(TAPJob.PARAM_UPLOAD, new StringParamController(TAPJob.PARAM_UPLOAD)); if (!mapParamControllers.containsKey(TAPJob.PARAM_FORMAT)) - mapParamControllers.put(TAPJob.PARAM_FORMAT, new FormatController<R>(service)); + mapParamControllers.put(TAPJob.PARAM_FORMAT, new FormatController(service)); if (!mapParamControllers.containsKey(TAPJob.PARAM_MAX_REC)) mapParamControllers.put(TAPJob.PARAM_MAX_REC, new MaxRecController(service)); diff --git a/src/tap/resource/Availability.java b/src/tap/resource/Availability.java index 73832f8c6fb939ba0770769cfd2406a45fd3dcd1..1da467efa05d65b20ffc94cac44e33a7181d1f42 100644 --- a/src/tap/resource/Availability.java +++ b/src/tap/resource/Availability.java @@ -33,17 +33,18 @@ public class Availability implements TAPResource, VOSIResource { public static final String RESOURCE_NAME = "availability"; - private final ServiceConnection<?> service; + private final ServiceConnection service; protected String accessURL = getName(); - protected Availability(ServiceConnection<?> service){ + protected Availability(ServiceConnection service){ this.service = service; } - public ServiceConnection<?> getService(){ + public ServiceConnection getService(){ return service; } + @Override public final void setTAPBaseURL(String baseURL){ accessURL = ((baseURL == null) ? "" : (baseURL + "/")) + getName(); } diff --git a/src/tap/resource/Capabilities.java b/src/tap/resource/Capabilities.java index be6ff926f639ac43b5859f3aa9f94ff026f345f3..4cbdba0d562cbe9f7b7dc282c2edf1dd0967c4bf 100644 --- a/src/tap/resource/Capabilities.java +++ b/src/tap/resource/Capabilities.java @@ -32,15 +32,16 @@ public class Capabilities implements TAPResource, VOSIResource { public static final String RESOURCE_NAME = "capabilities"; - private final TAP<?> tap; + private final TAP tap; protected String accessURL = getName(); - public Capabilities(TAP<?> tap){ + public Capabilities(TAP tap){ this.tap = tap; } /** */ + @Override public final void setTAPBaseURL(String baseURL){ accessURL = ((baseURL == null) ? "" : (baseURL + "/")) + getName(); } diff --git a/src/tap/resource/Sync.java b/src/tap/resource/Sync.java index 71be76f48da2c1312fe70a6497fd646a06603d25..43825dc1be79ef22dd6116cc7e91b22c729dc621 100644 --- a/src/tap/resource/Sync.java +++ b/src/tap/resource/Sync.java @@ -20,14 +20,15 @@ package tap.resource; */ import java.io.IOException; + import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import tap.TAPJob; import tap.ServiceConnection; import tap.TAPException; +import tap.TAPJob; import tap.TAPSyncJob; import tap.parameters.TAPParameters; import uws.UWSException; @@ -38,11 +39,11 @@ public class Sync implements TAPResource { protected String accessURL = null; - protected final ServiceConnection<?> service; + protected final ServiceConnection service; protected final Capabilities capabilities; - public Sync(ServiceConnection<?> service, Capabilities capabilities){ + public Sync(ServiceConnection service, Capabilities capabilities){ this.service = service; this.capabilities = capabilities; } diff --git a/src/tap/resource/TAP.java b/src/tap/resource/TAP.java index 3ad3a555d13bc52bf1060a4839bab34d5ce32998..db12b135546bdb6d21b5a649d805bb4e62a16127 100644 --- a/src/tap/resource/TAP.java +++ b/src/tap/resource/TAP.java @@ -55,11 +55,11 @@ import uws.service.UWSService; import uws.service.UWSUrl; import uws.service.error.ServiceErrorWriter; -public class TAP< R > implements VOSIResource { +public class TAP implements VOSIResource { private static final long serialVersionUID = 1L; - protected final ServiceConnection<R> service; + protected final ServiceConnection service; protected final Map<String,TAPResource> resources; @@ -69,7 +69,7 @@ public class TAP< R > implements VOSIResource { protected ServiceErrorWriter errorWriter; - public TAP(ServiceConnection<R> serviceConnection) throws UWSException, TAPException{ + public TAP(ServiceConnection serviceConnection) throws UWSException, TAPException{ service = serviceConnection; resources = new HashMap<String,TAPResource>(); @@ -89,11 +89,12 @@ public class TAP< R > implements VOSIResource { getUWS().setErrorWriter(errorWriter); if (service.uploadEnabled()){ - DBConnection<?> dbConn = null; + DBConnection dbConn = null; try{ dbConn = service.getFactory().createDBConnection("TAP(ServiceConnection)"); - dbConn.dropSchema("TAP_UPLOAD"); - dbConn.createSchema("TAP_UPLOAD"); + // TODO CLEAN ACTION: DROP SCHEMA! + /*dbConn.dropSchema("TAP_UPLOAD"); + dbConn.createSchema("TAP_UPLOAD");*/ }catch(TAPException e){ throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Error while creating the schema TAP_UPLOAD !"); }finally{ @@ -178,8 +179,8 @@ public class TAP< R > implements VOSIResource { xml.append("\t\t<description>ADQL 2.0</description>\n"); xml.append("\t</language>\n"); - Iterator<OutputFormat<R>> itFormats = service.getOutputFormats(); - OutputFormat<R> formatter; + Iterator<OutputFormat> itFormats = service.getOutputFormats(); + OutputFormat formatter; while(itFormats.hasNext()){ formatter = itFormats.next(); xml.append("\t<outputFormat>\n"); diff --git a/src/tap/upload/Uploader.java b/src/tap/upload/Uploader.java index 651a198c2b8a5b8c518766a47eb3fdccda73a809..76bbab4842d705a9ab6417f48bf62f0198f79117 100644 --- a/src/tap/upload/Uploader.java +++ b/src/tap/upload/Uploader.java @@ -26,113 +26,126 @@ import java.io.InputStream; import tap.ServiceConnection; import tap.ServiceConnection.LimitUnit; import tap.TAPException; +import tap.data.DataReadException; +import tap.data.VOTableIterator; import tap.db.DBConnection; -import tap.db.DBException; +import tap.metadata.TAPColumn; +import tap.metadata.TAPDM; import tap.metadata.TAPSchema; import tap.metadata.TAPTable; -import tap.metadata.TAPTypes; -import tap.metadata.VotType; -import cds.savot.model.DataBinaryReader; -import cds.savot.model.FieldSet; -import cds.savot.model.SavotBinary; -import cds.savot.model.SavotField; -import cds.savot.model.SavotResource; -import cds.savot.model.SavotTR; -import cds.savot.model.SavotTableData; -import cds.savot.model.TRSet; -import cds.savot.pull.SavotPullEngine; -import cds.savot.pull.SavotPullParser; import com.oreilly.servlet.multipart.ExceededSizeException; /** + * <p>Let upload properly given VOTable inputs.</p> * - * @author Grégory Mantelet (CDS;ARI) - gmantele@ari.uni-heidelberg.de - * @version 1.1 (03/2014) + * <p>This class manages particularly the upload limit in rows + * (thanks to {@link VOTableIterator}) and in bytes (thanks to a {@link LimitedSizeInputStream}).</p> + * + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) */ public class Uploader { - protected final ServiceConnection<?> service; - protected final DBConnection<?> dbConn; + /** Specification of the TAP service. */ + protected final ServiceConnection service; + /** Connection to the "database" (which lets upload the content of any given VOTable). */ + protected final DBConnection dbConn; + /** Limit on the number of rows allowed to be uploaded in once (whatever is the number of tables). */ protected final int nbRowsLimit; + /** Limit on the number of bytes allowed to be uploaded in once (whatever is the number of tables). */ protected final int nbBytesLimit; + /** Number of rows already loaded. */ protected int nbRows = 0; - public Uploader(final ServiceConnection<?> service, final DBConnection<?> dbConn) throws TAPException{ + /** + * Build an {@link Uploader} object. + * + * @param service Specification of the TAP service using this uploader. + * @param dbConn A valid (open) connection to the "database". + * + * @throws TAPException If any error occurs while building this {@link Uploader}. + */ + public Uploader(final ServiceConnection service, final DBConnection dbConn) throws TAPException{ + // NULL tests: if (service == null) throw new NullPointerException("The given ServiceConnection is NULL !"); if (dbConn == null) throw new NullPointerException("The given DBConnection is NULL !"); + // Set the service and database connections: this.service = service; - this.dbConn = dbConn; - if (service.uploadEnabled()){ - if (service.getUploadLimitType()[1] == LimitUnit.rows){ - nbRowsLimit = ((service.getUploadLimit()[1] > 0) ? service.getUploadLimit()[1] : -1); + // Ensure UPLOAD is allowed by the TAP service specification... + if (this.service.uploadEnabled()){ + // ...and set the rows or bytes limit: + if (this.service.getUploadLimitType()[1] == LimitUnit.rows){ + nbRowsLimit = ((this.service.getUploadLimit()[1] > 0) ? this.service.getUploadLimit()[1] : -1); nbBytesLimit = -1; }else{ - nbBytesLimit = ((service.getUploadLimit()[1] > 0) ? service.getUploadLimit()[1] : -1); + nbBytesLimit = ((this.service.getUploadLimit()[1] > 0) ? this.service.getUploadLimit()[1] : -1); nbRowsLimit = -1; } }else throw new TAPException("Upload aborted: this functionality is disabled in this TAP service!"); } + /** + * Upload all the given VOTable inputs. + * + * @param loaders Array of tables to upload. + * + * @return A {@link TAPSchema} containing the list and the description of all uploaded tables. + * + * @throws TAPException If any error occurs while reading the VOTable inputs or while uploading the table into the "database". + * + * @see DBConnection#addUploadedTable(TAPTable, tap.data.TableIterator) + */ public TAPSchema upload(final TableLoader[] loaders) throws TAPException{ - // Begin a DB transaction: - dbConn.startTransaction(); - - TAPSchema uploadSchema = new TAPSchema("TAP_UPLOAD"); + TAPSchema uploadSchema = new TAPSchema(TAPDM.UPLOADSCHEMA.getLabel()); InputStream votable = null; String tableName = null; - nbRows = 0; try{ for(TableLoader loader : loaders){ tableName = loader.tableName; + + // Open a stream toward the VOTable: votable = loader.openStream(); + // Set a byte limit if one is required: if (nbBytesLimit > 0) votable = new LimitedSizeInputStream(votable, nbBytesLimit); - // start parsing the VOTable: - SavotPullParser parser = new SavotPullParser(votable, SavotPullEngine.SEQUENTIAL, null); + // Start reading the VOTable: + VOTableIterator dataIt = new VOTableIterator(votable); - SavotResource resource = parser.getNextResource(); - if (resource == null) - throw new TAPException("Incorrect VOTable format !"); + // Define the table to upload: + TAPColumn[] columns = dataIt.getMetadata(); + TAPTable table = new TAPTable(tableName); + table.setDBName(tableName + "_" + System.currentTimeMillis()); + for(TAPColumn col : columns) + table.addColumn(col); - FieldSet fields = resource.getFieldSet(0); + // Add the table to the TAP_UPLOAD schema: + uploadSchema.addTable(table); - // 1st STEP: Convert the VOTable metadata into DBTable: - TAPTable tapTable = fetchTableMeta(tableName, System.currentTimeMillis() + "", fields); - uploadSchema.addTable(tapTable); - - // 2nd STEP: Create the corresponding table in the database: - dbConn.createTable(tapTable); - - // 3rd STEP: Load rows into this table: - SavotBinary binary = resource.getData(0).getBinary(); - if (binary != null) - loadTable(tapTable, fields, binary); - else - loadTable(tapTable, fields, resource.getData(0).getTableData()); + // Create and fill the corresponding table in the database: + dbConn.addUploadedTable(table, dataIt, nbRowsLimit); + // Close the VOTable stream: votable.close(); } - }catch(DBException dbe){ - dbConn.cancelTransaction(); // ROLLBACK - throw dbe; - }catch(ExceededSizeException ese){ - dbConn.cancelTransaction(); // ROLLBACK - throw new TAPException("Upload limit exceeded ! You can upload at most " + ((nbBytesLimit > 0) ? (nbBytesLimit + " bytes.") : (nbRowsLimit + " rows."))); + }catch(DataReadException dre){ + if (dre.getCause() instanceof ExceededSizeException) + throw new TAPException("Upload limit exceeded ! You can upload at most " + ((nbBytesLimit > 0) ? (nbBytesLimit + " bytes.") : (nbRowsLimit + " rows."))); + else + throw new TAPException("Error while reading the VOTable \"" + tableName + "\": " + dre.getMessage(), dre); }catch(IOException ioe){ - dbConn.cancelTransaction(); // ROLLBACK - throw new TAPException("Error while reading the VOTable of \"" + tableName + "\" !", ioe); + throw new TAPException("Error while reading the VOTable of \"" + tableName + "\"!", ioe); }catch(NullPointerException npe){ - dbConn.cancelTransaction(); // ROLLBACK if (votable != null && votable instanceof LimitedSizeInputStream) throw new TAPException("Upload limit exceeded ! You can upload at most " + ((nbBytesLimit > 0) ? (nbBytesLimit + " bytes.") : (nbRowsLimit + " rows."))); else @@ -146,77 +159,8 @@ public class Uploader { } } - // Commit modifications: - try{ - dbConn.endTransaction(); - }finally{ - dbConn.close(); - } - + // Return the TAP_UPLOAD schema (containing just the uploaded tables): return uploadSchema; } - private TAPTable fetchTableMeta(final String tableName, final String userId, final FieldSet fields){ - TAPTable tapTable = new TAPTable(tableName); - tapTable.setDBName(tableName + "_" + userId); - - for(int j = 0; j < fields.getItemCount(); j++){ - SavotField field = (SavotField)fields.getItemAt(j); - int arraysize = TAPTypes.NO_SIZE; - if (field.getArraySize() == null || field.getArraySize().trim().isEmpty()) - arraysize = 1; - else if (field.getArraySize().equalsIgnoreCase("*")) - arraysize = TAPTypes.STAR_SIZE; - else{ - try{ - arraysize = Integer.parseInt(field.getArraySize()); - }catch(NumberFormatException nfe){ - service.getLogger().warning("Invalid array-size in the uploaded table \"" + tableName + "\" for the field \"" + field.getName() + "\": \"" + field.getArraySize() + "\" ! It will be considered as \"*\" !"); - } - } - tapTable.addColumn(field.getName(), field.getDescription(), field.getUnit(), field.getUcd(), field.getUtype(), new VotType(field.getDataType(), arraysize, field.getXtype()), false, false, false); - } - - return tapTable; - } - - private int loadTable(final TAPTable tapTable, final FieldSet fields, final SavotBinary binary) throws TAPException, ExceededSizeException{ - // Read the raw binary data: - DataBinaryReader reader = null; - try{ - reader = new DataBinaryReader(binary.getStream(), fields, false); - while(reader.next()){ - if (nbRowsLimit > 0 && nbRows >= nbRowsLimit) - throw new ExceededSizeException(); - dbConn.insertRow(reader.getTR(), tapTable); - nbRows++; - } - }catch(ExceededSizeException ese){ - throw ese; - }catch(IOException se){ - throw new TAPException("Error while reading the binary data of the VOTable of \"" + tapTable.getADQLName() + "\" !", se); - }finally{ - try{ - if (reader != null) - reader.close(); - }catch(IOException ioe){ - ; - } - } - - return nbRows; - } - - private int loadTable(final TAPTable tapTable, final FieldSet fields, final SavotTableData data) throws TAPException, ExceededSizeException{ - TRSet rows = data.getTRs(); - for(int i = 0; i < rows.getItemCount(); i++){ - if (nbRowsLimit > 0 && nbRows >= nbRowsLimit) - throw new ExceededSizeException(); - dbConn.insertRow((SavotTR)rows.getItemAt(i), tapTable); - nbRows++; - } - - return nbRows; - } - } diff --git a/test/adql/SearchColumnListTest.java b/test/adql/SearchColumnListTest.java index 776a3435b09af5b21a2a71623e5594e28db1fbe6..0a3b48dd9cc72e015313e50ba29cf54a7f468139 100644 --- a/test/adql/SearchColumnListTest.java +++ b/test/adql/SearchColumnListTest.java @@ -5,6 +5,11 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import tap.metadata.TAPColumn; +import tap.metadata.TAPSchema; +import tap.metadata.TAPTable; +import tap.metadata.TAPType; +import tap.metadata.TAPType.TAPDatatype; import adql.db.DBColumn; import adql.db.DBCommonColumn; import adql.db.DBTable; @@ -13,9 +18,6 @@ import adql.db.exception.UnresolvedJoin; import adql.parser.ParseException; import adql.query.IdentifierField; import adql.query.operand.ADQLColumn; -import tap.metadata.TAPColumn; -import tap.metadata.TAPSchema; -import tap.metadata.TAPTable; public class SearchColumnListTest { @@ -29,16 +31,16 @@ public class SearchColumnListTest { TAPTable tableD = new TAPTable("D", "TABLE", "NATURAL JOIN Test table", null); // Describe its columns: - tableA.addColumn(new TAPColumn("id", "Object ID")); - tableA.addColumn(new TAPColumn("txta", "Text of table A")); - tableB.addColumn(new TAPColumn("id", "Object ID")); - tableB.addColumn(new TAPColumn("txtb", "Text of table B")); - tableC.addColumn(new TAPColumn("Id", "Object ID")); - tableC.addColumn(new TAPColumn("txta", "Text of table A")); - tableC.addColumn(new TAPColumn("txtc", "Text of table C")); - tableD.addColumn(new TAPColumn("id", "Object ID")); - tableD.addColumn(new TAPColumn("txta", "Text of table A")); - tableD.addColumn(new TAPColumn("txtd", "Text of table D")); + tableA.addColumn(new TAPColumn("id", new TAPType(TAPDatatype.VARCHAR), "Object ID")); + tableA.addColumn(new TAPColumn("txta", new TAPType(TAPDatatype.VARCHAR), "Text of table A")); + tableB.addColumn(new TAPColumn("id", new TAPType(TAPDatatype.VARCHAR), "Object ID")); + tableB.addColumn(new TAPColumn("txtb", new TAPType(TAPDatatype.VARCHAR), "Text of table B")); + tableC.addColumn(new TAPColumn("Id", new TAPType(TAPDatatype.VARCHAR), "Object ID")); + tableC.addColumn(new TAPColumn("txta", new TAPType(TAPDatatype.VARCHAR), "Text of table A")); + tableC.addColumn(new TAPColumn("txtc", new TAPType(TAPDatatype.VARCHAR), "Text of table C")); + tableD.addColumn(new TAPColumn("id", new TAPType(TAPDatatype.VARCHAR), "Object ID")); + tableD.addColumn(new TAPColumn("txta", new TAPType(TAPDatatype.VARCHAR), "Text of table A")); + tableD.addColumn(new TAPColumn("txtd", new TAPType(TAPDatatype.VARCHAR), "Text of table D")); // List all available tables: TAPSchema schema = new TAPSchema("public"); diff --git a/test/tap/formatter/JSONFormatTest.java b/test/tap/formatter/JSONFormatTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a037575c54bafb49cb09a88c44d954fbc86e5495 --- /dev/null +++ b/test/tap/formatter/JSONFormatTest.java @@ -0,0 +1,247 @@ +package tap.formatter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +import org.json.JSONObject; +import org.json.JSONTokener; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import tap.ServiceConnection; +import tap.TAPExecutionReport; +import tap.TAPFactory; +import tap.TAPJob; +import tap.data.ResultSetTableIterator; +import tap.data.TableIterator; +import tap.file.TAPFileManager; +import tap.log.TAPLog; +import tap.metadata.TAPColumn; +import tap.metadata.TAPMetadata; +import tap.metadata.TAPType; +import tap.metadata.TAPType.TAPDatatype; +import tap.parameters.TAPParameters; +import testtools.DBTools; +import uws.service.UserIdentifier; + +/** + * <p>Test the JSONFormat function {@link JSONFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p> + * + * <p>2 test ares done: 1 with an overflow and another without.</p> + * + * @author Grégory Mantelet (ARI) + * @version 2.0 (07/2014) + */ +public class JSONFormatTest { + + private static Connection conn; + private static ServiceConnection serviceConn; + private static TAPColumn[] resultingColumns; + private static File jsonFile = new File("/home/gmantele/Desktop/json_test.json"); + + @BeforeClass + public static void setUpBeforeClass() throws Exception{ + conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd"); + serviceConn = new ServiceConnectionTest(); + + resultingColumns = new TAPColumn[4]; + resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR)); + resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null); + resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null); + resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null); + + if (!jsonFile.exists()) + jsonFile.createNewFile(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception{ + DBTools.closeConnection(conn); + jsonFile.delete(); + } + + @Test + public void testWriteResult(){ + try{ + ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "100"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + JSONFormat formatter = new JSONFormat(serviceConn); + OutputStream output = new BufferedOutputStream(new FileOutputStream(jsonFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + JSONTokener tok = new JSONTokener(new FileInputStream(jsonFile)); + JSONObject obj = (JSONObject)tok.nextValue(); + assertEquals(obj.getJSONArray("data").length(), 10); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + } + } + + @Test + public void testWriteResultWithOverflow(){ + ResultSet rs = null; + try{ + rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "5"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + JSONFormat formatter = new JSONFormat(serviceConn); + OutputStream output = new BufferedOutputStream(new FileOutputStream(jsonFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + JSONTokener tok = new JSONTokener(new FileInputStream(jsonFile)); + JSONObject obj = (JSONObject)tok.nextValue(); + assertEquals(obj.getJSONArray("data").length(), 5); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + }finally{ + if (rs != null){ + try{ + rs.close(); + }catch(SQLException e){ + System.err.println("Can not close the RESULTSET!"); + e.printStackTrace(); + } + } + } + } + + private static class ServiceConnectionTest implements ServiceConnection { + + @Override + public int[] getOutputLimit(){ + return new int[]{1000000,1000000}; + } + + @Override + public LimitUnit[] getOutputLimitType(){ + return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes}; + } + + @Override + public String getProviderName(){ + return null; + } + + @Override + public String getProviderDescription(){ + return null; + } + + @Override + public boolean isAvailable(){ + return true; + } + + @Override + public String getAvailability(){ + return "AVAILABLE"; + } + + @Override + public int[] getRetentionPeriod(){ + return null; + } + + @Override + public int[] getExecutionDuration(){ + return null; + } + + @Override + public UserIdentifier getUserIdentifier(){ + return null; + } + + @Override + public boolean uploadEnabled(){ + return false; + } + + @Override + public int[] getUploadLimit(){ + return null; + } + + @Override + public LimitUnit[] getUploadLimitType(){ + return null; + } + + @Override + public int getMaxUploadSize(){ + return 0; + } + + @Override + public TAPMetadata getTAPMetadata(){ + return null; + } + + @Override + public Collection<String> getCoordinateSystems(){ + return null; + } + + @Override + public TAPLog getLogger(){ + return null; + } + + @SuppressWarnings("rawtypes") + @Override + public TAPFactory getFactory(){ + return null; + } + + @Override + public TAPFileManager getFileManager(){ + return null; + } + + @Override + public Iterator<OutputFormat> getOutputFormats(){ + return null; + } + + @Override + public OutputFormat getOutputFormat(String mimeOrAlias){ + return null; + } + + } + +} diff --git a/test/tap/formatter/SVFormatTest.java b/test/tap/formatter/SVFormatTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f44dda5f4196ab6f24a069b35a64a16f1a0b4b3f --- /dev/null +++ b/test/tap/formatter/SVFormatTest.java @@ -0,0 +1,267 @@ +package tap.formatter; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import tap.ServiceConnection; +import tap.TAPExecutionReport; +import tap.TAPFactory; +import tap.TAPJob; +import tap.data.ResultSetTableIterator; +import tap.data.TableIterator; +import tap.file.TAPFileManager; +import tap.log.TAPLog; +import tap.metadata.TAPColumn; +import tap.metadata.TAPMetadata; +import tap.metadata.TAPType; +import tap.metadata.TAPType.TAPDatatype; +import tap.parameters.TAPParameters; +import testtools.DBTools; +import uws.service.UserIdentifier; + +/** + * <p>Test the SVFormat function {@link SVFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p> + * + * <p>2 test ares done: 1 with an overflow and another without.</p> + * + * @author Grégory Mantelet (ARI) + * @version 2.0 (07/2014) + */ +public class SVFormatTest { + + private static Connection conn; + private static ServiceConnection serviceConn; + private static TAPColumn[] resultingColumns; + private static File svFile = new File("/home/gmantele/Desktop/sv_test.txt"); + + @BeforeClass + public static void setUpBeforeClass() throws Exception{ + conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd"); + serviceConn = new ServiceConnectionTest(); + + resultingColumns = new TAPColumn[4]; + resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR)); + resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null); + resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null); + resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null); + + if (!svFile.exists()) + svFile.createNewFile(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception{ + DBTools.closeConnection(conn); + svFile.delete(); + } + + @Test + public void testWriteResult(){ + try{ + ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "100"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + SVFormat formatter = new SVFormat(serviceConn, SVFormat.COMMA_SEPARATOR); + OutputStream output = new BufferedOutputStream(new FileOutputStream(svFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + svFile.getAbsolutePath() + "\""}; + assertTrue(executeCommand(cmd).trim().equals("11")); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + } + } + + @Test + public void testWriteResultWithOverflow(){ + ResultSet rs = null; + try{ + rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "5"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + SVFormat formatter = new SVFormat(serviceConn, SVFormat.COMMA_SEPARATOR); + OutputStream output = new BufferedOutputStream(new FileOutputStream(svFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + svFile.getAbsolutePath() + "\""}; + assertTrue(executeCommand(cmd).trim().equals("6")); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + }finally{ + if (rs != null){ + try{ + rs.close(); + }catch(SQLException e){ + System.err.println("Can not close the RESULTSET!"); + e.printStackTrace(); + } + } + } + } + + private String executeCommand(String[] command){ + + StringBuffer output = new StringBuffer(); + + Process p; + try{ + p = Runtime.getRuntime().exec(command); + p.waitFor(); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + + String line = ""; + while((line = reader.readLine()) != null){ + output.append(line + "\n"); + } + + }catch(Exception e){ + e.printStackTrace(); + } + + return output.toString(); + + } + + private static class ServiceConnectionTest implements ServiceConnection { + + @Override + public int[] getOutputLimit(){ + return new int[]{1000000,1000000}; + } + + @Override + public LimitUnit[] getOutputLimitType(){ + return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes}; + } + + @Override + public String getProviderName(){ + return null; + } + + @Override + public String getProviderDescription(){ + return null; + } + + @Override + public boolean isAvailable(){ + return true; + } + + @Override + public String getAvailability(){ + return "AVAILABLE"; + } + + @Override + public int[] getRetentionPeriod(){ + return null; + } + + @Override + public int[] getExecutionDuration(){ + return null; + } + + @Override + public UserIdentifier getUserIdentifier(){ + return null; + } + + @Override + public boolean uploadEnabled(){ + return false; + } + + @Override + public int[] getUploadLimit(){ + return null; + } + + @Override + public LimitUnit[] getUploadLimitType(){ + return null; + } + + @Override + public int getMaxUploadSize(){ + return 0; + } + + @Override + public TAPMetadata getTAPMetadata(){ + return null; + } + + @Override + public Collection<String> getCoordinateSystems(){ + return null; + } + + @Override + public TAPLog getLogger(){ + return null; + } + + @SuppressWarnings("rawtypes") + @Override + public TAPFactory getFactory(){ + return null; + } + + @Override + public TAPFileManager getFileManager(){ + return null; + } + + @Override + public Iterator<OutputFormat> getOutputFormats(){ + return null; + } + + @Override + public OutputFormat getOutputFormat(String mimeOrAlias){ + return null; + } + + } + +} diff --git a/test/tap/formatter/TextFormatTest.java b/test/tap/formatter/TextFormatTest.java new file mode 100644 index 0000000000000000000000000000000000000000..27b5f38302285a7548435b1b5f58babb6497259c --- /dev/null +++ b/test/tap/formatter/TextFormatTest.java @@ -0,0 +1,267 @@ +package tap.formatter; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import tap.ServiceConnection; +import tap.TAPExecutionReport; +import tap.TAPFactory; +import tap.TAPJob; +import tap.data.ResultSetTableIterator; +import tap.data.TableIterator; +import tap.file.TAPFileManager; +import tap.log.TAPLog; +import tap.metadata.TAPColumn; +import tap.metadata.TAPMetadata; +import tap.metadata.TAPType; +import tap.metadata.TAPType.TAPDatatype; +import tap.parameters.TAPParameters; +import testtools.DBTools; +import uws.service.UserIdentifier; + +/** + * <p>Test the TestFormat function {@link TestFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p> + * + * <p>2 test ares done: 1 with an overflow and another without.</p> + * + * @author Grégory Mantelet (ARI) + * @version 2.0 (07/2014) + */ +public class TextFormatTest { + + private static Connection conn; + private static ServiceConnection serviceConn; + private static TAPColumn[] resultingColumns; + private static File textFile = new File("/home/gmantele/Desktop/text_test.txt"); + + @BeforeClass + public static void setUpBeforeClass() throws Exception{ + conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd"); + serviceConn = new ServiceConnectionTest(); + + resultingColumns = new TAPColumn[4]; + resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR)); + resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null); + resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null); + resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null); + + if (!textFile.exists()) + textFile.createNewFile(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception{ + DBTools.closeConnection(conn); + //textFile.delete(); + } + + @Test + public void testWriteResult(){ + try{ + ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "100"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + TextFormat formatter = new TextFormat(serviceConn); + OutputStream output = new BufferedOutputStream(new FileOutputStream(textFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + textFile.getAbsolutePath() + "\""}; + assertTrue(executeCommand(cmd).trim().equals("12")); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + } + } + + @Test + public void testWriteResultWithOverflow(){ + ResultSet rs = null; + try{ + rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "5"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + TextFormat formatter = new TextFormat(serviceConn); + OutputStream output = new BufferedOutputStream(new FileOutputStream(textFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + textFile.getAbsolutePath() + "\""}; + assertTrue(executeCommand(cmd).trim().equals("7")); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + }finally{ + if (rs != null){ + try{ + rs.close(); + }catch(SQLException e){ + System.err.println("Can not close the RESULTSET!"); + e.printStackTrace(); + } + } + } + } + + private String executeCommand(String[] command){ + + StringBuffer output = new StringBuffer(); + + Process p; + try{ + p = Runtime.getRuntime().exec(command); + p.waitFor(); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + + String line = ""; + while((line = reader.readLine()) != null){ + output.append(line + "\n"); + } + + }catch(Exception e){ + e.printStackTrace(); + } + + return output.toString(); + + } + + private static class ServiceConnectionTest implements ServiceConnection { + + @Override + public int[] getOutputLimit(){ + return new int[]{1000000,1000000}; + } + + @Override + public LimitUnit[] getOutputLimitType(){ + return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes}; + } + + @Override + public String getProviderName(){ + return null; + } + + @Override + public String getProviderDescription(){ + return null; + } + + @Override + public boolean isAvailable(){ + return true; + } + + @Override + public String getAvailability(){ + return "AVAILABLE"; + } + + @Override + public int[] getRetentionPeriod(){ + return null; + } + + @Override + public int[] getExecutionDuration(){ + return null; + } + + @Override + public UserIdentifier getUserIdentifier(){ + return null; + } + + @Override + public boolean uploadEnabled(){ + return false; + } + + @Override + public int[] getUploadLimit(){ + return null; + } + + @Override + public LimitUnit[] getUploadLimitType(){ + return null; + } + + @Override + public int getMaxUploadSize(){ + return 0; + } + + @Override + public TAPMetadata getTAPMetadata(){ + return null; + } + + @Override + public Collection<String> getCoordinateSystems(){ + return null; + } + + @Override + public TAPLog getLogger(){ + return null; + } + + @SuppressWarnings("rawtypes") + @Override + public TAPFactory getFactory(){ + return null; + } + + @Override + public TAPFileManager getFileManager(){ + return null; + } + + @Override + public Iterator<OutputFormat> getOutputFormats(){ + return null; + } + + @Override + public OutputFormat getOutputFormat(String mimeOrAlias){ + return null; + } + + } + +} diff --git a/test/tap/formatter/VOTableFormatTest.java b/test/tap/formatter/VOTableFormatTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f5f0d174554b2c8cfbad9fa07f4a55b805172663 --- /dev/null +++ b/test/tap/formatter/VOTableFormatTest.java @@ -0,0 +1,275 @@ +package tap.formatter; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import tap.ServiceConnection; +import tap.TAPExecutionReport; +import tap.TAPFactory; +import tap.TAPJob; +import tap.data.ResultSetTableIterator; +import tap.data.TableIterator; +import tap.file.TAPFileManager; +import tap.log.TAPLog; +import tap.metadata.TAPColumn; +import tap.metadata.TAPMetadata; +import tap.metadata.TAPType; +import tap.metadata.TAPType.TAPDatatype; +import tap.parameters.TAPParameters; +import testtools.DBTools; +import uws.service.UserIdentifier; + +/** + * <p>Test the VOTableFormat function {@link VOTableFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p> + * + * <p>2 test ares done: 1 with an overflow and another without.</p> + * + * @author Grégory Mantelet (ARI) + * @version 2.0 (07/2014) + */ +public class VOTableFormatTest { + + private static Connection conn; + private static ServiceConnection serviceConn; + private static TAPColumn[] resultingColumns; + private static File votableFile = new File("/home/gmantele/Desktop/votable_test.xml"); + + @BeforeClass + public static void setUpBeforeClass() throws Exception{ + conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd"); + serviceConn = new ServiceConnectionTest(); + + resultingColumns = new TAPColumn[4]; + resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR)); + resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null); + resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null); + resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null); + + if (!votableFile.exists()) + votableFile.createNewFile(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception{ + DBTools.closeConnection(conn); + votableFile.delete(); + } + + @Test + public void testWriteResult(){ + try{ + ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "100"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + VOTableFormat formatter = new VOTableFormat(serviceConn); + OutputStream output = new BufferedOutputStream(new FileOutputStream(votableFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + // note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l": + String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; + assertTrue(executeCommand(cmd).trim().equals("10")); + + cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\" />\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; + assertTrue(executeCommand(cmd).trim().equals("0")); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + } + } + + @Test + public void testWriteResultWithOverflow(){ + ResultSet rs = null; + try{ + rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;"); + + HashMap<String,Object> tapParams = new HashMap<String,Object>(1); + tapParams.put(TAPJob.PARAM_MAX_REC, "5"); + TAPParameters params = new TAPParameters(serviceConn, tapParams); + TAPExecutionReport report = new TAPExecutionReport("123456A", true, params); + report.resultingColumns = resultingColumns; + + TableIterator it = new ResultSetTableIterator(rs); + + VOTableFormat formatter = new VOTableFormat(serviceConn); + OutputStream output = new BufferedOutputStream(new FileOutputStream(votableFile)); + formatter.writeResult(it, output, report, Thread.currentThread()); + output.close(); + + // note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l": + String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; + assertTrue(executeCommand(cmd).trim().equals("5")); + + cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\" />\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; + assertTrue(executeCommand(cmd).trim().equals("1")); + + }catch(Exception t){ + t.printStackTrace(); + fail("Unexpected exception!"); + }finally{ + if (rs != null){ + try{ + rs.close(); + }catch(SQLException e){ + System.err.println("Can not close the RESULTSET!"); + e.printStackTrace(); + } + } + } + } + + private String executeCommand(String[] command){ + + StringBuffer output = new StringBuffer(); + + Process p; + try{ + p = Runtime.getRuntime().exec(command); + p.waitFor(); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + + String line = ""; + while((line = reader.readLine()) != null){ + output.append(line + "\n"); + } + + }catch(Exception e){ + e.printStackTrace(); + } + + return output.toString(); + + } + + private static class ServiceConnectionTest implements ServiceConnection { + + @Override + public int[] getOutputLimit(){ + return new int[]{1000000,1000000}; + } + + @Override + public LimitUnit[] getOutputLimitType(){ + return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes}; + } + + @Override + public String getProviderName(){ + return null; + } + + @Override + public String getProviderDescription(){ + return null; + } + + @Override + public boolean isAvailable(){ + return true; + } + + @Override + public String getAvailability(){ + return "AVAILABLE"; + } + + @Override + public int[] getRetentionPeriod(){ + return null; + } + + @Override + public int[] getExecutionDuration(){ + return null; + } + + @Override + public UserIdentifier getUserIdentifier(){ + return null; + } + + @Override + public boolean uploadEnabled(){ + return false; + } + + @Override + public int[] getUploadLimit(){ + return null; + } + + @Override + public LimitUnit[] getUploadLimitType(){ + return null; + } + + @Override + public int getMaxUploadSize(){ + return 0; + } + + @Override + public TAPMetadata getTAPMetadata(){ + return null; + } + + @Override + public Collection<String> getCoordinateSystems(){ + return null; + } + + @Override + public TAPLog getLogger(){ + return null; + } + + @SuppressWarnings("rawtypes") + @Override + public TAPFactory getFactory(){ + return null; + } + + @Override + public TAPFileManager getFileManager(){ + return null; + } + + @Override + public Iterator<OutputFormat> getOutputFormats(){ + return null; + } + + @Override + public OutputFormat getOutputFormat(String mimeOrAlias){ + return null; + } + + } + +} diff --git a/test/testtools/DBTools.java b/test/testtools/DBTools.java index ca032c05a6877a18d34a851be833c24013b96e76..a5ef0fe5d0befee6ed5cab4e61771ce6a5b30a96 100644 --- a/test/testtools/DBTools.java +++ b/test/testtools/DBTools.java @@ -1,4 +1,5 @@ package testtools; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; @@ -118,6 +119,9 @@ public final class DBTools { }catch(InterruptedException e){ System.err.println("WARNING: can't wait/sleep before testing the connection close status! [" + e.getMessage() + "]"); } + // TODO DEBUG MSG + if (conn.isClosed()) + System.out.println("[DEBUG] Connection closed!"); return conn.isClosed(); }else return true;