diff --git a/src/tap/formatter/FITSFormat.java b/src/tap/formatter/FITSFormat.java index 13c5b85e06e3a0dda7d549ecead3ce063703b43c..212cfc90731cc35d39f341d392a834d69bd0b6c4 100644 --- a/src/tap/formatter/FITSFormat.java +++ b/src/tap/formatter/FITSFormat.java @@ -2,21 +2,22 @@ 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 2014-2015 - Astronomisches Rechen Institut (ARI) + * + * Copyright 2014-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Astronomisches Rechen Institut (ARI) */ import java.io.IOException; @@ -34,24 +35,27 @@ import uk.ac.starlink.table.StoragePolicy; /** * Format any given query (table) result into FITS. - * - * @author Grégory Mantelet (ARI) - * @version 2.1 (11/2015) + * + * @author Grégory Mantelet (CDS;ARI) + * @version 2.4 (08/2020) * @since 2.0 */ public class FITSFormat implements OutputFormat { - /** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */ + /** The {@link ServiceConnection} to use (for the log and to have some + * information about the service (particularly: name, description). */ protected final ServiceConnection service; /** * Creates a FITS formatter. - * - * @param service The service to use (for the log and to have some information about the service (particularly: name, description). - * - * @throws NullPointerException If the given service connection is <code>null</code>. + * + * @param service The service to use (for the log and to have some + * information about the service (particularly: name, + * description). + * + * @throws NullPointerException If the given service connection is NULL. */ - public FITSFormat(final ServiceConnection service) throws NullPointerException{ + public FITSFormat(final ServiceConnection service) throws NullPointerException { if (service == null) throw new NullPointerException("The given service connection is NULL !"); @@ -59,27 +63,27 @@ public class FITSFormat implements OutputFormat { } @Override - public String getMimeType(){ + public String getMimeType() { return "application/fits"; } @Override - public String getShortMimeType(){ + public String getShortMimeType() { return "fits"; } @Override - public String getDescription(){ + public String getDescription() { return null; } @Override - public String getFileExtension(){ + public String getFileExtension() { return "fits"; } @Override - public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, IOException, InterruptedException{ + public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, IOException, InterruptedException { // Extract the columns' metadata: ColumnInfo[] colInfos = VOTableFormat.toColumnInfos(result, execReport, thread); @@ -87,7 +91,20 @@ public class FITSFormat implements OutputFormat { LimitedStarTable table = new LimitedStarTable(result, colInfos, execReport.parameters.getMaxRec(), thread); // Copy the table on disk (or in memory if the table is short): - StarTable copyTable = StoragePolicy.PREFER_DISK.copyTable(table); + StarTable copyTable; + try { + copyTable = StoragePolicy.PREFER_DISK.copyTable(table); + } catch(IOException ioe) { + /* In case of time out, LimitedStarTable makes copyTable to stop by + * throwing an IOException. In such case, this IOException has to be + * interpreted as a normal interruption: */ + if (thread.isInterrupted()) + throw new InterruptedException(); + /* Otherwise, the error has to be managed properly (so, wrap it + * inside a TAPException): */ + else + throw new TAPException("Unexpected error while formatting the result!", ioe); + } if (thread.isInterrupted()) throw new InterruptedException(); diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java index 7252cb8486577dfd846e26ce663daebd16c49422..9360e6dcc410c555090c31bfdea84c3eff06d414 100644 --- a/src/tap/formatter/VOTableFormat.java +++ b/src/tap/formatter/VOTableFormat.java @@ -16,7 +16,7 @@ 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-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS) + * Copyright 2012-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS) * Astronomisches Rechen Institut (ARI) */ @@ -54,49 +54,70 @@ import uk.ac.starlink.votable.VOStarTable; import uk.ac.starlink.votable.VOTableVersion; /** - * <p>Format any given query (table) result into VOTable.</p> + * Format any given query (table) result into VOTable. * * <p> - * Format and version of the resulting VOTable can be provided in parameters at the construction time. - * This formatter is using STIL. So all formats and versions managed by STIL are also here. - * Basically, you have the following formats: TABLEDATA, BINARY, BINARY2 (only when using VOTable v1.3) and FITS. + * Format and version of the resulting VOTable can be provided in parameters at + * the construction time. This formatter is using STIL. So all formats and + * versions managed by STIL are also here. Basically, you have the following + * formats: TABLEDATA, BINARY, BINARY2 (only when using VOTable v1.3) and FITS. * The versions are: 1.0, 1.1, 1.2 and 1.3. * </p> * - * <p>Note: The MIME type is automatically set in function of the given VOTable serialization:</p> + * <p><i><b>Note:</b> + * The MIME type is automatically set in function of the given VOTable + * serialization: + * </i></p> * <ul> * <li><b>none or unknown</b>: equivalent to BINARY</li> - * <li><b>BINARY</b>: "application/x-votable+xml" = "votable"</li> - * <li><b>BINARY2</b>: "application/x-votable+xml;serialization=BINARY2" = "votable/b2"</li> - * <li><b>TABLEDATA</b>: "application/x-votable+xml;serialization=TABLEDATA" = "votable/td"</li> - * <li><b>FITS</b>: "application/x-votable+xml;serialization=FITS" = "votable/fits"</li> + * <li><b>BINARY</b>: "application/x-votable+xml" + * = "votable"</li> + * <li><b>BINARY2</b>: "application/x-votable+xml;serialization=BINARY2" + * = "votable/b2"</li> + * <li><b>TABLEDATA</b>: "application/x-votable+xml;serialization=TABLEDATA" + * = "votable/td"</li> + * <li><b>FITS</b>: "application/x-votable+xml;serialization=FITS" + * = "votable/fits"</li> * </ul> - * <p>It is however possible to change these default values thanks to {@link #setMimeType(String, String)}.</p> + * <p> + * It is however possible to change these default values thanks to + * {@link #setMimeType(String, String)}. + * </p> * - * <p>In addition of the INFO elements for QUERY_STATUS="OK" and QUERY_STATUS="OVERFLOW", two additional INFO elements are written:</p> + * <p> + * In addition of the INFO elements for QUERY_STATUS="OK", + * QUERY_STATUS="OVERFLOW" and QUERY_STATUS="ERROR", two additional INFO + * elements are written: + * </p> * <ul> - * <li>PROVIDER = {@link ServiceConnection#getProviderName()} and {@link ServiceConnection#getProviderDescription()}</li> + * <li>PROVIDER = {@link ServiceConnection#getProviderName()} and + * {@link ServiceConnection#getProviderDescription()}</li> * <li>QUERY = the ADQL query at the origin of this result.</li> * </ul> * * <p> - * Furthermore, this formatter provides a function to format an error in VOTable: {@link #writeError(String, Map, PrintWriter)}. - * This is useful for TAP which requires to return in VOTable any error that occurs while any operation. - * <i>See {@link DefaultTAPErrorWriter} for more details.</i> + * Furthermore, this formatter provides a function to format an error in + * VOTable: {@link #writeError(String, Map, PrintWriter)}. This is useful for + * TAP which requires to return in VOTable any error that occurs while any + * operation. <i>See {@link DefaultTAPErrorWriter} for more details.</i> * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.3 (03/2019) + * @version 2.4 (08/2020) */ public class VOTableFormat implements OutputFormat { - /** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */ + /** The {@link ServiceConnection} to use (for the log and to have some + * information about the service (particularly: name, description). */ protected final ServiceConnection service; - /** Format of the VOTable data part in which data must be formatted. Possible values are: TABLEDATA, BINARY, BINARY2 or FITS. By default, it is set to BINARY. */ + /** Format of the VOTable data part in which data must be formatted. + * Possible values are: TABLEDATA, BINARY, BINARY2 or FITS. + * By default, it is set to BINARY. */ protected final DataFormat votFormat; - /** VOTable version in which table data must be formatted. By default, it is set to v13. */ + /** VOTable version in which table data must be formatted. + * By default, it is set to v13. */ protected final VOTableVersion votVersion; /** MIME type associated with this format. */ @@ -106,63 +127,92 @@ public class VOTableFormat implements OutputFormat { protected String shortMimeType; /** - * <p>Creates a VOTable formatter.</p> + * Creates a VOTable formatter. * - * <p><i>Note: - * The MIME type is automatically set to "application/x-votable+xml" = "votable". - * It is however possible to change this default value thanks to {@link #setMimeType(String, String)}. + * <p><i><b>Note:</b> + * The MIME type is automatically set to "application/x-votable+xml" = + * "votable". It is however possible to change this default value thanks to + * {@link #setMimeType(String, String)}. * </i></p> * - * @param service The service to use (for the log and to have some information about the service (particularly: name, description). + * @param service The service to use (for the log and to have some + * information about the service (particularly: name, + * description). * - * @throws NullPointerException If the given service connection is <code>null</code>. + * @throws NullPointerException If the given service connection is NULL. */ - public VOTableFormat(final ServiceConnection service) throws NullPointerException{ + public VOTableFormat(final ServiceConnection service) throws NullPointerException { this(service, null, null); } /** - * <p>Creates a VOTable formatter.</p> + * Creates a VOTable formatter. * - * <i>Note: The MIME type is automatically set in function of the given VOTable serialization:</i> + * <p><i><b>Note:</b> + * The MIME type is automatically set in function of the given VOTable + * serialization: + * </i></p> * <ul> * <li><i><b>none or unknown</b>: equivalent to BINARY</i></li> - * <li><i><b>BINARY</b>: "application/x-votable+xml" = "votable"</i></li> - * <li><i><b>BINARY2</b>: "application/x-votable+xml;serialization=BINARY2" = "votable/b2"</i></li> - * <li><i><b>TABLEDATA</b>: "application/x-votable+xml;serialization=TABLEDATA" = "votable/td"</i></li> - * <li><i><b>FITS</b>: "application/x-votable+xml;serialization=FITS" = "votable/fits"</i></li> + * <li><i><b>BINARY</b>: "application/x-votable+xml" + * = "votable"</i></li> + * <li><i><b>BINARY2</b>: "application/x-votable+xml;serialization=BINARY2" + * = "votable/b2"</i></li> + * <li><i><b>TABLEDATA</b>: "application/x-votable+xml;serialization=TABLEDATA" + * = "votable/td"</i></li> + * <li><i><b>FITS</b>: "application/x-votable+xml;serialization=FITS" + * = "votable/fits"</i></li> * </ul> - * <p><i>It is however possible to change these default values thanks to {@link #setMimeType(String, String)}.</i></p> + * <p><i> + * It is however possible to change these default values thanks to + * {@link #setMimeType(String, String)}. + * </i></p> * - * @param service The service to use (for the log and to have some information about the service (particularly: name, description). - * @param votFormat Serialization of the VOTable data part. (TABLEDATA, BINARY, BINARY2 or FITS). + * @param service The service to use (for the log and to have some + * information about the service (particularly: name, + * description). + * @param votFormat Serialization of the VOTable data part. + * (TABLEDATA, BINARY, BINARY2 or FITS). * - * @throws NullPointerException If the given service connection is <code>null</code>. + * @throws NullPointerException If the given service connection is NULL. */ - public VOTableFormat(final ServiceConnection service, final DataFormat votFormat) throws NullPointerException{ + public VOTableFormat(final ServiceConnection service, final DataFormat votFormat) throws NullPointerException { this(service, votFormat, null); } /** - * <p>Creates a VOTable formatter.</p> + * Creates a VOTable formatter. * - * <i>Note: The MIME type is automatically set in function of the given VOTable serialization:</i> + * <p><i><b>Note:</b> + * The MIME type is automatically set in function of the given VOTable + * serialization: + * </i></p> * <ul> * <li><i><b>none or unknown</b>: equivalent to BINARY</i></li> - * <li><i><b>BINARY</b>: "application/x-votable+xml" = "votable"</i></li> - * <li><i><b>BINARY2</b>: "application/x-votable+xml;serialization=BINARY2" = "votable/b2"</i></li> - * <li><i><b>TABLEDATA</b>: "application/x-votable+xml;serialization=TABLEDATA" = "votable/td"</i></li> - * <li><i><b>FITS</b>: "application/x-votable+xml;serialization=FITS" = "votable/fits"</i></li> + * <li><i><b>BINARY</b>: "application/x-votable+xml" + * = "votable"</i></li> + * <li><i><b>BINARY2</b>: "application/x-votable+xml;serialization=BINARY2" + * = "votable/b2"</i></li> + * <li><i><b>TABLEDATA</b>: "application/x-votable+xml;serialization=TABLEDATA" + * = "votable/td"</i></li> + * <li><i><b>FITS</b>: "application/x-votable+xml;serialization=FITS" + * = "votable/fits"</i></li> * </ul> - * <p><i>It is however possible to change these default values thanks to {@link #setMimeType(String, String)}.</i></p> + * <p><i> + * It is however possible to change these default values thanks to + * {@link #setMimeType(String, String)}. + * </i></p> * - * @param service The service to use (for the log and to have some information about the service (particularly: name, description). - * @param votFormat Serialization of the VOTable data part. (TABLEDATA, BINARY, BINARY2 or FITS). - * @param votVersion Version of the resulting VOTable. + * @param service The service to use (for the log and to have some + * information about the service (particularly: name, + * description). + * @param votFormat Serialization of the VOTable data part. + * (TABLEDATA, BINARY, BINARY2 or FITS). + * @param votVersion Version of the resulting VOTable. * - * @throws NullPointerException If the given service connection is <code>null</code>. + * @throws NullPointerException If the given service connection is NULL. */ - public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion) throws NullPointerException{ + public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion) throws NullPointerException { if (service == null) throw new NullPointerException("The given service connection is NULL!"); @@ -173,43 +223,47 @@ public class VOTableFormat implements OutputFormat { this.votVersion = (votVersion == null) ? VOTableVersion.V13 : votVersion; // Deduce automatically the MIME type and its short expression: - if (this.votFormat.equals(DataFormat.BINARY)){ + if (this.votFormat.equals(DataFormat.BINARY)) { this.mimeType = "application/x-votable+xml"; this.shortMimeType = "votable"; - }else if (this.votFormat.equals(DataFormat.BINARY2)){ + } else if (this.votFormat.equals(DataFormat.BINARY2)) { this.mimeType = "application/x-votable+xml;serialization=BINARY2"; this.shortMimeType = "votable/b2"; - }else if (this.votFormat.equals(DataFormat.TABLEDATA)){ + } else if (this.votFormat.equals(DataFormat.TABLEDATA)) { this.mimeType = "application/x-votable+xml;serialization=TABLEDATA"; this.shortMimeType = "votable/td"; - }else if (this.votFormat.equals(DataFormat.FITS)){ + } else if (this.votFormat.equals(DataFormat.FITS)) { this.mimeType = "application/x-votable+xml;serialization=FITS"; this.shortMimeType = "votable/fits"; - }else{ + } else { this.mimeType = "application/x-votable+xml"; this.shortMimeType = "votable"; } } @Override - public final String getMimeType(){ + public final String getMimeType() { return mimeType; } @Override - public final String getShortMimeType(){ + public final String getShortMimeType() { return shortMimeType; } /** - * <p>Set the MIME type associated with this format.</p> + * Set the MIME type associated with this format. * - * <p><i>Note: NULL means no modification of the current value:</i></p> + * <p><i><b>Note:</b> + * NULL means no modification of the current value: + * </i></p> * - * @param mimeType Full MIME type of this VOTable format. <i>note: if NULL, the MIME type is not modified.</i> - * @param shortForm Short form of this MIME type. <i>note: if NULL, the short MIME type is not modified.</i> + * @param mimeType Full MIME type of this VOTable format. + * <i>note: if NULL, the MIME type is not modified.</i> + * @param shortForm Short form of this MIME type. + * <i>note: if NULL, the short MIME type is not modified.</i> */ - public final void setMimeType(final String mimeType, final String shortForm){ + public final void setMimeType(final String mimeType, final String shortForm) { if (mimeType != null) this.mimeType = mimeType; if (shortForm != null) @@ -221,7 +275,7 @@ public class VOTableFormat implements OutputFormat { * * @return The data format. */ - public final DataFormat getVotSerialization(){ + public final DataFormat getVotSerialization() { return votFormat; } @@ -230,27 +284,29 @@ public class VOTableFormat implements OutputFormat { * * @return The VOTable version. */ - public final VOTableVersion getVotVersion(){ + public final VOTableVersion getVotVersion() { return votVersion; } @Override - public String getDescription(){ + public String getDescription() { return null; } @Override - public String getFileExtension(){ + public String getFileExtension() { return "xml"; } /** - * <p>Write the given error message as VOTable document.</p> - * - * <p><i>Note: - * In the TAP protocol, all errors must be returned as VOTable. The class {@link DefaultTAPErrorWriter} is in charge of the management - * and reporting of all errors. It is calling this function while the error message to display to the user is ready and - * must be written in the HTTP response. + * Write the given error message as VOTable document. + * + * <p><i><b>Note:</b> + * In the TAP protocol, all errors must be returned as VOTable. The class + * {@link DefaultTAPErrorWriter} is in charge of the management and + * reporting of all errors. It is calling this function while the error + * message to display to the user is ready and must be written in the HTTP + * response. * </i></p> * * <p>Here is the XML format of this VOTable error:</p> @@ -267,14 +323,15 @@ public class VOTableFormat implements OutputFormat { * </pre> * * @param message Error message to display to the user. - * @param otherInfo List of other additional information to display. <i>optional</i> + * @param otherInfo List of other additional information to display. + * <i>optional</i> * @param writer Stream in which the VOTable error must be written. * * @throws IOException If any error occurs while writing in the given output. * * @since 2.0 */ - public void writeError(final String message, final Map<String, String> otherInfo, final PrintWriter writer) throws IOException{ + public void writeError(final String message, final Map<String, String> otherInfo, final PrintWriter writer) throws IOException { BufferedWriter out = new BufferedWriter(writer); // Set the root VOTABLE node: @@ -292,25 +349,25 @@ public class VOTableFormat implements OutputFormat { out.newLine(); // Append the PROVIDER information (if any): [OPTIONAL] - if (service.getProviderName() != null){ + if (service.getProviderName() != null) { out.write("<INFO name=\"PROVIDER\"" + VOSerializer.formatAttribute("value", service.getProviderName()) + ">" + ((service.getProviderDescription() == null) ? "" : VOSerializer.formatText(service.getProviderDescription())) + "</INFO>"); out.newLine(); } // Append the ADQL query at the origin of this result: [OPTIONAL] - if (otherInfo != null){ + if (otherInfo != null) { Iterator<Map.Entry<String, String>> it = otherInfo.entrySet().iterator(); - while(it.hasNext()){ + while(it.hasNext()) { Map.Entry<String, String> entry = it.next(); - if (entry.getValue() != null){ - if (entry.getValue().startsWith("\n")){ + if (entry.getValue() != null) { + if (entry.getValue().startsWith("\n")) { int sep = entry.getValue().substring(1).indexOf('\n'); if (sep < 0) sep = 0; else sep++; out.write("<INFO " + VOSerializer.formatAttribute("name", entry.getKey()) + VOSerializer.formatAttribute("value", entry.getValue().substring(1, sep)) + ">\n" + entry.getValue().substring(sep + 1) + "\n</INFO>"); - }else + } else out.write("<INFO " + VOSerializer.formatAttribute("name", entry.getKey()) + VOSerializer.formatAttribute("value", entry.getValue()) + "/>"); out.newLine(); } @@ -329,7 +386,7 @@ public class VOTableFormat implements OutputFormat { } @Override - public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, IOException, InterruptedException{ + public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, IOException, InterruptedException { ColumnInfo[] colInfos = toColumnInfos(queryResult, execReport, thread); /* Turns the result set into a table. */ @@ -341,29 +398,69 @@ public class VOTableFormat implements OutputFormat { /* if FITS, copy the table on disk (or in memory if the table is short): * (note: this is needed because STIL needs at least 2 passes on this * table to format it correctly in FITS format) */ - if (votFormat == DataFormat.FITS) - voser = VOSerializer.makeSerializer(votFormat, votVersion, StoragePolicy.PREFER_DISK.copyTable(table)); + if (votFormat == DataFormat.FITS) { + try { + voser = VOSerializer.makeSerializer(votFormat, votVersion, StoragePolicy.PREFER_DISK.copyTable(table)); + } catch(IOException ioe) { + /* As in the class FITSFormat, the caught IOException may be due + * to an interruption from LimitedStarTable. In such case, + * propagate the interruption: */ + if (thread.isInterrupted()) + throw new InterruptedException(); + /* Any other error should be properly wrapped: */ + else + throw new TAPException("Unexpected error while formatting the result!", ioe); + } + } // otherwise, just use the default VOTable serializer: else voser = VOSerializer.makeSerializer(votFormat, votVersion, table); + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(output)); /* Write header. */ writeHeader(votVersion, execReport, out); - if (thread.isInterrupted()) - throw new InterruptedException(); - /* Write table element. */ - voser.writeInlineTableElement(out); - execReport.nbRows = table.getNbReadRows(); - out.flush(); - - if (thread.isInterrupted()) - throw new InterruptedException(); + if (!thread.isInterrupted()) { + try { + voser.writeInlineTableElement(out); + execReport.nbRows = table.getNbReadRows(); + out.flush(); + } catch(Exception ex) { + /* If synchronous, the partially written VOTable should be + * properly closed and an error INFO should be appended: */ + if (execReport.synchronous) { + if (votFormat != DataFormat.TABLEDATA) { + out.write("</STREAM>\n</BINARY>\n</DATA>\n</TABLE>"); + out.newLine(); + } + out.write("<INFO name=\"QUERY_STATUS\" value=\"ERROR\">Result truncated due to an unexpected grave error: " + VOSerializer.formatText(ex.getMessage()) + "</INFO>"); + out.newLine(); + } + // If asynchronous, just propagate the error: + else { + if (ex instanceof TAPException || ex instanceof IOException || ex instanceof InterruptedException) + throw ex; + else + throw new TAPException(ex); + } + } + } - /* Check for overflow and write INFO if required. */ - if (table.lastSequenceOverflowed()){ + /* If Timed Out... */ + if (thread.isInterrupted()) { + // ...if synchronous, end properly the VOTable with an error INFO: + if (execReport != null && execReport.synchronous) { + out.write("<INFO name=\"QUERY_STATUS\" value=\"ERROR\">Time out! (Hint: Try running this query in asynchronous mode to get the complete result)</INFO>"); + out.newLine(); + } + // ...if asynchronous, merely propagate the interruption: + else + throw new InterruptedException(); + } + /* If Overflow, declare this in an INFO: */ + else if (table.lastSequenceOverflowed()) { out.write("<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\"/>"); out.newLine(); } @@ -378,16 +475,19 @@ public class VOTableFormat implements OutputFormat { } /** - * <p>Writes the first VOTable nodes/elements preceding the data: VOTABLE, RESOURCE and 3 INFOS (QUERY_STATUS, PROVIDER, QUERY).</p> + * Writes the first VOTable nodes/elements preceding the data: VOTABLE, + * RESOURCE and 3 INFOS (QUERY_STATUS, PROVIDER, QUERY). * * @param votVersion Target VOTable version. * @param execReport The report of the query execution. * @param out Writer in which the root node must be written. * - * @throws IOException If there is an error while writing the root node in the given Writer. - * @throws TAPException If there is any other error (by default: never happen). + * @throws IOException If there is an error while writing the root node in + * the given Writer. + * @throws TAPException If there is any other error + * (by default: never happen). */ - protected void writeHeader(final VOTableVersion votVersion, final TAPExecutionReport execReport, final BufferedWriter out) throws IOException, TAPException{ + protected void writeHeader(final VOTableVersion votVersion, final TAPExecutionReport execReport, final BufferedWriter out) throws IOException, TAPException { // Set the root VOTABLE node: out.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); out.newLine(); @@ -403,34 +503,34 @@ public class VOTableFormat implements OutputFormat { out.newLine(); // Append the PROVIDER information (if any): [OPTIONAL] - if (service.getProviderName() != null){ + if (service.getProviderName() != null) { out.write("<INFO name=\"PROVIDER\"" + VOSerializer.formatAttribute("value", service.getProviderName()) + ">" + ((service.getProviderDescription() == null) ? "" : VOSerializer.formatText(service.getProviderDescription())) + "</INFO>"); out.newLine(); } // Append the ADQL query at the origin of this result: [OPTIONAL] String adqlQuery = execReport.parameters.getQuery(); - if (adqlQuery != null){ + if (adqlQuery != null) { out.write("<INFO name=\"QUERY\"" + VOSerializer.formatAttribute("value", adqlQuery) + "/>"); out.newLine(); } // Append the fixed ADQL query, if any: [OPTIONAL] String fixedQuery = execReport.fixedQuery; - if (fixedQuery != null){ + if (fixedQuery != null) { out.write("<INFO name=\"QUERY_AFTER_AUTO_FIX\"" + VOSerializer.formatAttribute("value", fixedQuery) + "/>"); out.newLine(); } // Insert the definition of all used coordinate systems: HashSet<String> insertedCoosys = new HashSet<String>(10); - for(DBColumn col : execReport.resultingColumns){ + for(DBColumn col : execReport.resultingColumns) { // ignore columns with no coossys: - if (col instanceof TAPColumn && ((TAPColumn)col).getCoosys() != null){ + if (col instanceof TAPColumn && ((TAPColumn)col).getCoosys() != null) { // get its coosys: TAPCoosys coosys = ((TAPColumn)col).getCoosys(); // insert the coosys definition ONLY if not already done because of another column: - if (!insertedCoosys.contains(coosys.getId())){ + if (!insertedCoosys.contains(coosys.getId())) { // write the VOTable serialization of this coordinate system definition: out.write("<COOSYS" + VOSerializer.formatAttribute("ID", coosys.getId())); if (coosys.getSystem() != null) @@ -453,17 +553,21 @@ public class VOTableFormat implements OutputFormat { /** * Writes fields' metadata of the given query result. * - * @param result The query result from whose fields' metadata must be written. + * @param result The query result from whose fields' metadata must be + * written. * @param execReport The report of the query execution. * @param thread The thread which asked for the result writing. * - * @return Extracted field's metadata, or NULL if no metadata have been found (theoretically, it never happens). + * @return Extracted field's metadata, or NULL if no metadata have been + * found (theoretically, it never happens). * - * @throws IOException If there is an error while writing the metadata. - * @throws TAPException If there is any other error. - * @throws InterruptedException If the given thread has been interrupted. + * @throws IOException If there is an error while writing the + * metadata. + * @throws TAPException If there is any other error. + * @throws InterruptedException If the given thread has been + * interrupted. */ - public static final ColumnInfo[] toColumnInfos(final TableIterator result, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ + public static final ColumnInfo[] toColumnInfos(final TableIterator result, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException { // Get the metadata extracted/guesses from the ADQL query: DBColumn[] columnsFromQuery = execReport.resultingColumns; @@ -471,13 +575,13 @@ public class VOTableFormat implements OutputFormat { TAPColumn[] columnsFromResult = result.getMetadata(); int indField = 0; - if (columnsFromQuery != null){ + if (columnsFromQuery != null) { // Initialize the resulting array: ColumnInfo[] colInfos = new ColumnInfo[columnsFromQuery.length]; // For each column: - for(DBColumn field : columnsFromQuery){ + for(DBColumn field : columnsFromQuery) { // Try to build/get appropriate metadata for this field/column: TAPColumn colFromResult = (columnsFromResult != null && indField < columnsFromResult.length) ? columnsFromResult[indField] : null; @@ -490,41 +594,43 @@ public class VOTableFormat implements OutputFormat { } return colInfos; - }else + } else return null; } /** - * Try to get or otherwise to build appropriate metadata using those extracted from the ADQL query and those extracted from the result. + * 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 static final TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){ - if (typeFromQuery != null && typeFromQuery instanceof TAPColumn){ + protected static final TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult) { + if (typeFromQuery != null && typeFromQuery instanceof TAPColumn) { TAPColumn colMeta = (TAPColumn)typeFromQuery; if (colMeta.getDatatype().isUnknown() && typeFromResult != null && !typeFromResult.getDatatype().isUnknown()) colMeta.setDatatype(typeFromResult.getDatatype()); return colMeta; - }else if (typeFromResult != null){ + } else if (typeFromResult != null) { if (typeFromQuery != null) return (TAPColumn)typeFromResult.copy(typeFromQuery.getDBName(), typeFromQuery.getADQLName(), null); else return (TAPColumn)typeFromResult.copy(); - }else + } else return new TAPColumn((typeFromQuery != null) ? typeFromQuery.getADQLName() : "?", new DBType(DBDatatype.VARCHAR), "?"); } /** - * Convert the given {@link TAPColumn} object into a {@link ColumnInfo} object. + * Convert the given {@link TAPColumn} object into a {@link ColumnInfo} + * object. * * @param tapCol {@link TAPColumn} to convert into {@link ColumnInfo}. * * @return The corresponding {@link ColumnInfo}. */ - protected static final ColumnInfo getColumnInfo(final TAPColumn tapCol){ + protected static final ColumnInfo getColumnInfo(final TAPColumn tapCol) { // Get the VOTable type: VotType votType = new VotType(tapCol.getDatatype()); @@ -554,19 +660,20 @@ public class VOTableFormat implements OutputFormat { } /** - * Convert the VOTable datatype string into a corresponding {@link Class} object. + * Convert the VOTable datatype string into a corresponding {@link Class} + * object. * * @param datatype Value of the VOTable attribute "datatype". * @param arraysize Value of the VOTable attribute "arraysize". * * @return The corresponding {@link Class} object. */ - protected static final Class<?> getDatatypeClass(final VotDatatype datatype, final String arraysize){ + protected static final Class<?> getDatatypeClass(final VotDatatype datatype, final String arraysize) { // Determine whether this type is an array or not: boolean isScalar = arraysize == null || (arraysize.length() == 1 && arraysize.equals("1")); // Guess the corresponding Class object (see section "7.1.4 Data Types" of the STIL documentation): - switch(datatype){ + switch(datatype) { case BIT: return boolean[].class; case BOOLEAN: @@ -601,7 +708,7 @@ public class VOTableFormat implements OutputFormat { * * @return The corresponding {@link ColumnInfo} shape. */ - protected static final int[] getShape(final String arraysize){ + protected static final int[] getShape(final String arraysize) { /* * Note: multi-dimensional arrays are forbidden in the TAP library, * so no 'nxm...' is possible. @@ -616,10 +723,10 @@ public class VOTableFormat implements OutputFormat { return new int[]{ -1 }; // 'n' => {n}: - else{ - try{ + else { + try { return new int[]{ Integer.parseInt(arraysize) }; - }catch(NumberFormatException nfe){ + } catch(NumberFormatException nfe) { // if the given arraysize is incorrect (theoretically, never happens), it is like no arraysize has been provided: return new int[0]; } @@ -627,10 +734,9 @@ public class VOTableFormat implements OutputFormat { } /** - * <p> - * Special {@link StarTable} able to read a fixed maximum number of rows {@link TableIterator}. - * However, if no limit is provided, all rows are read. - * </p> + * Special {@link StarTable} able to read a fixed maximum number of rows + * {@link TableIterator}. However, if no limit is provided, all rows are + * read. * * @author Grégory Mantelet (CDS;ARI) * @version 2.1 (11/2015) @@ -647,17 +753,21 @@ public class VOTableFormat implements OutputFormat { /** Iterator over the data to read using this special {@link StarTable} */ private final TableIterator tableIt; - /** Thread covering this execution. If it is interrupted, the writing must stop as soon as possible. + /** Thread covering this execution. If it is interrupted, the writing + * must stop as soon as possible. * @since 2.1 */ private final Thread threadToWatch; - /** Limit on the number of rows to read. Over this limit, an "overflow" event occurs and {@link #overflow} is set to TRUE. */ + /** Limit on the number of rows to read. Over this limit, an "overflow" + * event occurs and {@link #overflow} is set to TRUE. */ private final long maxrec; - /** Indicates whether the maximum allowed number of rows has already been read or not. When true, no more row can be read. */ + /** Indicates whether the maximum allowed number of rows has already + * been read or not. When true, no more row can be read. */ private boolean overflow; - /** Last read row. If NULL, no row has been read or no more row is available. */ + /** Last read row. If NULL, no row has been read or no more row is + * available. */ private Object[] row = null; /** Number of rows read until now. */ @@ -666,12 +776,15 @@ public class VOTableFormat implements OutputFormat { /** * Build this special {@link StarTable}. * - * @param tableIt Data on which to iterate using this special {@link StarTable}. + * @param tableIt Data on which to iterate using this special + * {@link StarTable}. * @param colInfos Information about all columns. - * @param maxrec Limit on the number of rows to read. <i>(if negative, there will be no limit)</i> - * @param thread Parent thread. When an interruption is detected the writing must stop as soon as possible. + * @param maxrec Limit on the number of rows to read. + * <i>(if negative, there will be no limit)</i> + * @param thread Parent thread. When an interruption is detected the + * writing must stop as soon as possible. */ - LimitedStarTable(final TableIterator tableIt, final ColumnInfo[] colInfos, final long maxrec, final Thread thread){ + LimitedStarTable(final TableIterator tableIt, final ColumnInfo[] colInfos, final long maxrec, final Thread thread) { this.tableIt = tableIt; this.threadToWatch = thread; nbCol = colInfos.length; @@ -686,7 +799,7 @@ public class VOTableFormat implements OutputFormat { * * @return true if the last row sequence overflowed */ - public boolean lastSequenceOverflowed(){ + public boolean lastSequenceOverflowed() { return overflow; } @@ -695,27 +808,27 @@ public class VOTableFormat implements OutputFormat { * * @return Number of all read rows. */ - public int getNbReadRows(){ + public int getNbReadRows() { return nbRows; } @Override - public int getColumnCount(){ + public int getColumnCount() { return nbCol; } @Override - public ColumnInfo getColumnInfo(final int colInd){ + public ColumnInfo getColumnInfo(final int colInd) { return columnInfos[colInd]; } @Override - public long getRowCount(){ + public long getRowCount() { return -1; } @Override - public RowSequence getRowSequence() throws IOException{ + public RowSequence getRowSequence() throws IOException { overflow = false; row = new Object[nbCol]; @@ -723,24 +836,24 @@ public class VOTableFormat implements OutputFormat { long irow = -1; @Override - public boolean next() throws IOException{ + public boolean next() throws IOException { irow++; - try{ - if (!threadToWatch.isInterrupted() && (maxrec < 0 || irow < maxrec)){ + try { + if (!threadToWatch.isInterrupted() && (maxrec < 0 || irow < maxrec)) { boolean hasNext = tableIt.nextRow(); - if (hasNext){ + if (hasNext) { for(int i = 0; i < nbCol && tableIt.hasNextCol(); i++) row[i] = tableIt.nextCol(); nbRows++; - }else + } else row = null; return hasNext; - }else{ + } else { overflow = tableIt.nextRow(); row = null; return false; } - }catch(DataReadException dre){ + } catch(DataReadException dre) { if (dre.getCause() != null && dre.getCause() instanceof IOException) throw (IOException)(dre.getCause()); else @@ -749,17 +862,17 @@ public class VOTableFormat implements OutputFormat { } @Override - public Object[] getRow() throws IOException{ + public Object[] getRow() throws IOException { return row; } @Override - public Object getCell(int cellIndex) throws IOException{ + public Object getCell(int cellIndex) throws IOException { return row[cellIndex]; } @Override - public void close() throws IOException{ + public void close() throws IOException { } }; }