diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java index ccee023a839175f51337f6b4ab23856b0af9c106..cf3f7248774223213beabf2e250f5255c8f2fb74 100644 --- a/src/tap/formatter/VOTableFormat.java +++ b/src/tap/formatter/VOTableFormat.java @@ -2,21 +2,21 @@ 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-2017 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS) + * + * Copyright 2012-2019 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS) * Astronomisches Rechen Institut (ARI) */ @@ -29,6 +29,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; +import adql.db.DBColumn; +import adql.db.DBType; +import adql.db.DBType.DBDatatype; import tap.ServiceConnection; import tap.TAPException; import tap.TAPExecutionReport; @@ -44,24 +47,22 @@ import uk.ac.starlink.table.ColumnInfo; import uk.ac.starlink.table.DescribedValue; import uk.ac.starlink.table.RowSequence; import uk.ac.starlink.table.StarTable; +import uk.ac.starlink.table.StoragePolicy; import uk.ac.starlink.votable.DataFormat; import uk.ac.starlink.votable.VOSerializer; import uk.ac.starlink.votable.VOStarTable; import uk.ac.starlink.votable.VOTableVersion; -import adql.db.DBColumn; -import adql.db.DBType; -import adql.db.DBType.DBDatatype; /** * <p>Format any given query (table) result into VOTable.</p> - * + * * <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. * 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> * <ul> * <li><b>none or unknown</b>: equivalent to BINARY</li> @@ -71,21 +72,21 @@ import adql.db.DBType.DBDatatype; * <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>In addition of the INFO elements for QUERY_STATUS="OK" and QUERY_STATUS="OVERFLOW", two additional INFO elements are written:</p> * <ul> * <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> * </p> - * + * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (07/2017) + * @version 2.2 (03/2019) */ public class VOTableFormat implements OutputFormat { @@ -106,14 +107,14 @@ public class VOTableFormat implements OutputFormat { /** * <p>Creates a VOTable formatter.</p> - * + * * <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)}. * </i></p> - * + * * @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>. */ public VOTableFormat(final ServiceConnection service) throws NullPointerException{ @@ -122,7 +123,7 @@ public class VOTableFormat implements OutputFormat { /** * <p>Creates a VOTable formatter.</p> - * + * * <i>Note: The MIME type is automatically set in function of the given VOTable serialization:</i> * <ul> * <li><i><b>none or unknown</b>: equivalent to BINARY</i></li> @@ -132,10 +133,10 @@ public class VOTableFormat implements OutputFormat { * <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> - * + * * @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>. */ public VOTableFormat(final ServiceConnection service, final DataFormat votFormat) throws NullPointerException{ @@ -144,7 +145,7 @@ public class VOTableFormat implements OutputFormat { /** * <p>Creates a VOTable formatter.</p> - * + * * <i>Note: The MIME type is automatically set in function of the given VOTable serialization:</i> * <ul> * <li><i><b>none or unknown</b>: equivalent to BINARY</i></li> @@ -154,11 +155,11 @@ public class VOTableFormat implements OutputFormat { * <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> - * + * * @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>. */ public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion) throws NullPointerException{ @@ -202,9 +203,9 @@ public class VOTableFormat implements OutputFormat { /** * <p>Set the MIME type associated with this format.</p> - * + * * <p><i>Note: 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> */ @@ -217,7 +218,7 @@ public class VOTableFormat implements OutputFormat { /** * Get the set VOTable data serialization/format (e.g. BINARY, TABLEDATA). - * + * * @return The data format. */ public final DataFormat getVotSerialization(){ @@ -226,7 +227,7 @@ public class VOTableFormat implements OutputFormat { /** * Get the set VOTable version. - * + * * @return The VOTable version. */ public final VOTableVersion getVotVersion(){ @@ -245,13 +246,13 @@ public class VOTableFormat implements OutputFormat { /** * <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. * </i></p> - * + * * <p>Here is the XML format of this VOTable error:</p> * <pre> * <VOTABLE version="..." xmlns="..." > @@ -264,16 +265,16 @@ public class VOTableFormat implements OutputFormat { * </RESOURCE> * </VOTABLE> * </pre> - * + * * @param message Error message to display to the user. * @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: @@ -298,9 +299,9 @@ public class VOTableFormat implements OutputFormat { // Append the ADQL query at the origin of this result: [OPTIONAL] if (otherInfo != null){ - Iterator<Map.Entry<String,String>> it = otherInfo.entrySet().iterator(); + Iterator<Map.Entry<String, String>> it = otherInfo.entrySet().iterator(); while(it.hasNext()){ - Map.Entry<String,String> entry = it.next(); + Map.Entry<String, String> entry = it.next(); if (entry.getValue() != null){ if (entry.getValue().startsWith("\n")){ int sep = entry.getValue().substring(1).indexOf('\n'); @@ -333,10 +334,18 @@ public class VOTableFormat implements OutputFormat { /* Turns the result set into a table. */ LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec(), thread); - table.setName("result_"+execReport.jobID); + table.setName("result_" + execReport.jobID); /* Prepares the object that will do the serialization work. */ - VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table); + VOSerializer voser = null; + /* 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)); + // otherwise, just use the default VOTable serializer: + else + voser = VOSerializer.makeSerializer(votFormat, votVersion, table); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(output)); /* Write header. */ @@ -370,11 +379,11 @@ 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> - * + * * @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). */ @@ -405,7 +414,7 @@ public class VOTableFormat implements OutputFormat { out.write("<INFO name=\"QUERY\"" + VOSerializer.formatAttribute("value", adqlQuery) + "/>"); out.newLine(); } - + // Insert the definition of all used coordinate systems: HashSet<String> insertedCoosys = new HashSet<String>(10); for(DBColumn col : execReport.resultingColumns){ @@ -416,7 +425,7 @@ public class VOTableFormat implements OutputFormat { // insert the coosys definition ONLY if not already done because of another column: if (!insertedCoosys.contains(coosys.getId())){ // write the VOTable serialization of this coordinate system definition: - out.write("<COOSYS"+VOSerializer.formatAttribute("ID", coosys.getId())); + out.write("<COOSYS" + VOSerializer.formatAttribute("ID", coosys.getId())); if (coosys.getSystem() != null) out.write(VOSerializer.formatAttribute("system", coosys.getSystem())); if (coosys.getEquinox() != null) @@ -436,13 +445,13 @@ 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 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). - * + * * @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. @@ -480,10 +489,10 @@ public class VOTableFormat implements OutputFormat { /** * 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){ @@ -503,9 +512,9 @@ public class VOTableFormat implements OutputFormat { /** * 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){ @@ -529,7 +538,7 @@ public class VOTableFormat implements OutputFormat { colInfo.setUnitString(tapCol.getUnit()); colInfo.setUCD(tapCol.getUcd()); colInfo.setUtype(tapCol.getUtype()); - + // Set the coosys ref (if any): if (tapCol.getCoosys() != null) colInfo.setAuxDatum(new DescribedValue(VOStarTable.REF_INFO, tapCol.getCoosys().getId())); @@ -539,17 +548,17 @@ public class VOTableFormat implements OutputFormat { /** * Convert the VOTable datatype string into a corresponding {@link Class} object. - * - * @param datatype Value of the VOTable attribute "datatype". + * + * @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){ // 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): + // Guess the corresponding Class object (see section "7.1.4 Data Types" of the STIL documentation): switch(datatype){ case BIT: return boolean[].class; @@ -580,9 +589,9 @@ public class VOTableFormat implements OutputFormat { /** * Convert the given VOTable arraysize into a {@link ColumnInfo} shape. - * + * * @param arraysize Value of the VOTable attribute "arraysize". - * + * * @return The corresponding {@link ColumnInfo} shape. */ protected static final int[] getShape(final String arraysize){ @@ -597,12 +606,12 @@ public class VOTableFormat implements OutputFormat { // '*' or 'n*' => {-1}: else if (arraysize.charAt(arraysize.length() - 1) == '*') - return new int[]{-1}; + return new int[]{ -1 }; // 'n' => {n}: else{ try{ - return new int[]{Integer.parseInt(arraysize)}; + return new int[]{ Integer.parseInt(arraysize) }; }catch(NumberFormatException nfe){ // if the given arraysize is incorrect (theoretically, never happens), it is like no arraysize has been provided: return new int[0]; @@ -615,7 +624,7 @@ public class VOTableFormat implements OutputFormat { * 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> - * + * * @author Grégory Mantelet (CDS;ARI) * @version 2.1 (11/2015) * @since 2.0 @@ -649,7 +658,7 @@ public class VOTableFormat implements OutputFormat { /** * Build 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> @@ -703,7 +712,7 @@ public class VOTableFormat implements OutputFormat { overflow = false; row = new Object[nbCol]; - return new RowSequence(){ + return new RowSequence() { long irow = -1; @Override @@ -743,7 +752,8 @@ public class VOTableFormat implements OutputFormat { } @Override - public void close() throws IOException{} + public void close() throws IOException{ + } }; } }