From 3f59de36ef53dbfe815ddbb4118b9f7f6c83677a Mon Sep 17 00:00:00 2001 From: gmantele <gmantele@ari.uni-heidelberg.de> Date: Tue, 17 Feb 2015 19:40:36 +0100 Subject: [PATCH] [TAP] Add missing formats (particularly all the different VOTable serializations) in the TAP configuration file --- .../config/ConfigurableServiceConnection.java | 146 ++++++++++++++- src/tap/config/ConfigurableTAPServlet.java | 6 +- src/tap/config/TAPConfiguration.java | 6 + src/tap/config/tap_configuration_file.html | 48 ++--- src/tap/config/tap_full.properties | 22 ++- src/tap/formatter/VOTableFormat.java | 35 ++-- .../TestConfigurableServiceConnection.java | 167 +++++++++++++++++- 7 files changed, 380 insertions(+), 50 deletions(-) diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java index b13d82f..b60fd09 100644 --- a/src/tap/config/ConfigurableServiceConnection.java +++ b/src/tap/config/ConfigurableServiceConnection.java @@ -29,14 +29,20 @@ import static tap.config.TAPConfiguration.KEY_UDFS; import static tap.config.TAPConfiguration.KEY_UPLOAD_ENABLED; import static tap.config.TAPConfiguration.KEY_UPLOAD_MAX_FILE_SIZE; import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER; +import static tap.config.TAPConfiguration.VALUE_ALL; import static tap.config.TAPConfiguration.VALUE_ANY; import static tap.config.TAPConfiguration.VALUE_CSV; import static tap.config.TAPConfiguration.VALUE_DB; +import static tap.config.TAPConfiguration.VALUE_FITS; +import static tap.config.TAPConfiguration.VALUE_HTML; import static tap.config.TAPConfiguration.VALUE_JSON; import static tap.config.TAPConfiguration.VALUE_LOCAL; import static tap.config.TAPConfiguration.VALUE_NONE; import static tap.config.TAPConfiguration.VALUE_SV; +import static tap.config.TAPConfiguration.VALUE_TEXT; import static tap.config.TAPConfiguration.VALUE_TSV; +import static tap.config.TAPConfiguration.VALUE_VOT; +import static tap.config.TAPConfiguration.VALUE_VOTABLE; import static tap.config.TAPConfiguration.VALUE_XML; import static tap.config.TAPConfiguration.fetchClass; import static tap.config.TAPConfiguration.getProperty; @@ -54,14 +60,19 @@ import tap.ServiceConnection; import tap.TAPException; import tap.TAPFactory; import tap.db.DBConnection; +import tap.formatter.FITSFormat; +import tap.formatter.HTMLFormat; import tap.formatter.JSONFormat; import tap.formatter.OutputFormat; import tap.formatter.SVFormat; +import tap.formatter.TextFormat; import tap.formatter.VOTableFormat; import tap.log.DefaultTAPLog; import tap.log.TAPLog; import tap.metadata.TAPMetadata; import tap.metadata.TableSetParser; +import uk.ac.starlink.votable.DataFormat; +import uk.ac.starlink.votable.VOTableVersion; import uws.UWSException; import uws.service.UserIdentifier; import uws.service.file.LocalUWSFileManager; @@ -131,8 +142,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection { // 6. CONFIGURE OUTPUT: // default output format = VOTable: outputFormats = new ArrayList<OutputFormat>(1); - outputFormats.add(new VOTableFormat(this)); - // set additional output formats: + // set output formats: addOutputFormats(tapConfig); // set output limits: initOutputLimits(tapConfig); @@ -314,14 +324,39 @@ public final class ConfigurableServiceConnection implements ServiceConnection { private void addOutputFormats(final Properties tapConfig) throws TAPException{ // Fetch the value of the property for additional output formats: - String formats = TAPConfiguration.getProperty(tapConfig, KEY_OUTPUT_FORMATS); + String formats = getProperty(tapConfig, KEY_OUTPUT_FORMATS); + + // SPECIAL VALUE "ALL": + if (formats == null || formats.equalsIgnoreCase(VALUE_ALL)){ + outputFormats.add(new VOTableFormat(this, DataFormat.BINARY)); + outputFormats.add(new VOTableFormat(this, DataFormat.BINARY2)); + outputFormats.add(new VOTableFormat(this, DataFormat.TABLEDATA)); + outputFormats.add(new VOTableFormat(this, DataFormat.FITS)); + outputFormats.add(new FITSFormat(this)); + outputFormats.add(new SVFormat(this, ",", true)); + outputFormats.add(new SVFormat(this, "\t", true)); + outputFormats.add(new TextFormat(this)); + outputFormats.add(new HTMLFormat(this)); + return; + } + // LIST OF FORMATS: // Since it is a comma separated list of output formats, a loop will parse this list comma by comma: String f; - int indexSep; + int indexSep, indexLPar, indexRPar; + boolean hasVotableFormat = false; while(formats != null && formats.length() > 0){ // Get a format item from the list: indexSep = formats.indexOf(','); + // if a comma is after a left parenthesis + indexLPar = formats.indexOf('('); + if (indexSep > 0 && indexLPar > 0 && indexSep > indexLPar){ + indexRPar = formats.indexOf(')', indexLPar); + if (indexRPar > 0) + indexSep = formats.indexOf(',', indexRPar); + else + throw new TAPException("Missing right parenthesis in: \"" + formats + "\"!"); + } // no comma => only one format if (indexSep < 0){ f = formats; @@ -339,9 +374,18 @@ public final class ConfigurableServiceConnection implements ServiceConnection { } // Identify the format and append it to the output format list of the service: + // FITS + if (f.equalsIgnoreCase(VALUE_FITS)) + outputFormats.add(new FITSFormat(this)); // JSON - if (f.equalsIgnoreCase(VALUE_JSON)) + else if (f.equalsIgnoreCase(VALUE_JSON)) outputFormats.add(new JSONFormat(this)); + // HTML + else if (f.equalsIgnoreCase(VALUE_HTML)) + outputFormats.add(new HTMLFormat(this)); + // TEXT + else if (f.equalsIgnoreCase(VALUE_TEXT)) + outputFormats.add(new TextFormat(this)); // CSV else if (f.equalsIgnoreCase(VALUE_CSV)) outputFormats.add(new SVFormat(this, ",", true)); @@ -370,6 +414,18 @@ public final class ConfigurableServiceConnection implements ServiceConnection { }else throw new TAPException("Missing separator char/string for the SV output format: \"" + f + "\"!"); } + // VOTABLE + else if (f.toLowerCase().startsWith(VALUE_VOTABLE) || f.toLowerCase().startsWith(VALUE_VOT)){ + // Parse the format: + VOTableFormat votFormat = parseVOTableFormat(f); + + // Add the VOTable format: + outputFormats.add(votFormat); + + // Determine whether the MIME type is the VOTable expected one: + if (votFormat.getShortMimeType().equals("votable") || votFormat.getMimeType().equals("votable")) + hasVotableFormat = true; + } // custom OutputFormat else if (isClassPath(f)){ Class<? extends OutputFormat> userOutputFormatClass = fetchClass(f, KEY_OUTPUT_FORMATS, OutputFormat.class); @@ -387,6 +443,86 @@ public final class ConfigurableServiceConnection implements ServiceConnection { else throw new TAPException("Unknown output format: " + f); } + + // Add by default VOTable format if none is specified: + if (!hasVotableFormat) + outputFormats.add(new VOTableFormat(this)); + } + + private VOTableFormat parseVOTableFormat(final String propValue) throws TAPException{ + DataFormat serialization = null; + VOTableVersion votVersion = null; + String mimeType = null, shortMimeType = null; + + // Get the parameters, if any: + int beginSep = propValue.indexOf('('); + if (beginSep > 0){ + int endSep = propValue.indexOf(')'); + if (endSep <= beginSep) + throw new TAPException("Wrong output format specification syntax in: \"" + propValue + "\"! A VOTable parameters list must end with ')'."); + // split the parameters: + String[] params = propValue.substring(beginSep + 1, endSep).split(","); + if (params.length > 2) + throw new TAPException("Wrong number of parameters for the output format VOTable: \"" + propValue + "\"! Only two parameters may be provided: serialization and version."); + else if (params.length >= 1){ + // resolve the serialization format: + params[0] = params[0].trim().toLowerCase(); + if (params[0].length() == 0 || params[0].equals("b") || params[0].equals("binary")) + serialization = DataFormat.BINARY; + else if (params[0].equals("b2") || params[0].equals("binary2")) + serialization = DataFormat.BINARY2; + else if (params[0].equals("td") || params[0].equals("tabledata")) + serialization = DataFormat.TABLEDATA; + else if (params[0].equals("fits")) + serialization = DataFormat.FITS; + else + throw new TAPException("Unsupported VOTable serialization: \"" + params[0] + "\"! Accepted values: 'binary' (or 'b'), 'binary2' (or 'b2'), 'tabledata' (or 'td') and 'fits'."); + // resolve the version: + if (params.length == 2){ + params[1] = params[1].trim(); + if (params[1].equals("1.0") || params[1].equalsIgnoreCase("v1.0")) + votVersion = VOTableVersion.V10; + else if (params[1].equals("1.1") || params[1].equalsIgnoreCase("v1.1")) + votVersion = VOTableVersion.V11; + else if (params[1].equals("1.2") || params[1].equalsIgnoreCase("v1.2")) + votVersion = VOTableVersion.V12; + else if (params[1].equals("1.3") || params[1].equalsIgnoreCase("v1.3")) + votVersion = VOTableVersion.V13; + else + throw new TAPException("Unsupported VOTable version: \"" + params[1] + "\"! Accepted values: '1.0' (or 'v1.0'), '1.1' (or 'v1.1'), '1.2' (or 'v1.2') and '1.3' (or 'v1.3')."); + } + } + } + + // Get the MIME type and its alias, if any: + beginSep = propValue.indexOf(':'); + if (beginSep > 0){ + int endSep = propValue.indexOf(':', beginSep + 1); + if (endSep < 0) + endSep = propValue.length(); + // extract the MIME type, if any: + mimeType = propValue.substring(beginSep + 1, endSep).trim(); + if (mimeType.length() == 0) + mimeType = null; + // extract the short MIME type, if any: + if (endSep < propValue.length()){ + beginSep = endSep; + endSep = propValue.indexOf(':', beginSep + 1); + if (endSep >= 0) + throw new TAPException("Wrong output format specification syntax in: \"" + propValue + "\"! After a MIME type and a short MIME type, no more information is expected."); + else + endSep = propValue.length(); + shortMimeType = propValue.substring(beginSep + 1, endSep).trim(); + if (shortMimeType.length() == 0) + shortMimeType = null; + } + } + + // Create the VOTable format: + VOTableFormat votFormat = new VOTableFormat(this, serialization, votVersion); + votFormat.setMimeType(mimeType, shortMimeType); + + return votFormat; } private void initOutputLimits(final Properties tapConfig) throws TAPException{ diff --git a/src/tap/config/ConfigurableTAPServlet.java b/src/tap/config/ConfigurableTAPServlet.java index 72150f4..8260eb3 100644 --- a/src/tap/config/ConfigurableTAPServlet.java +++ b/src/tap/config/ConfigurableTAPServlet.java @@ -23,7 +23,9 @@ import static tap.config.TAPConfiguration.DEFAULT_TAP_CONF_FILE; import static tap.config.TAPConfiguration.KEY_HOME_PAGE; import static tap.config.TAPConfiguration.KEY_HOME_PAGE_MIME_TYPE; import static tap.config.TAPConfiguration.TAP_CONF_PARAMETER; +import static tap.config.TAPConfiguration.fetchClass; import static tap.config.TAPConfiguration.getProperty; +import static tap.config.TAPConfiguration.isClassPath; import java.io.File; import java.io.FileInputStream; @@ -114,11 +116,11 @@ public class ConfigurableTAPServlet extends HttpServlet { String propValue = getProperty(tapConf, KEY_HOME_PAGE); if (propValue != null){ // If it is a class path, replace the current home page by an instance of this class: - if (TAPConfiguration.isClassPath(propValue)){ + if (isClassPath(propValue)){ Class<? extends HomePage> newHomePage = null; try{ // ...fetch the class: - newHomePage = TAPConfiguration.fetchClass(propValue, KEY_HOME_PAGE, HomePage.class); + newHomePage = fetchClass(propValue, KEY_HOME_PAGE, HomePage.class); // ...get its constructor with TAP object: Constructor<? extends HomePage> constructor = newHomePage.getConstructor(TAP.class); // ...create a new instance and set it as new home page: diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java index 35a9658..b41f307 100644 --- a/src/tap/config/TAPConfiguration.java +++ b/src/tap/config/TAPConfiguration.java @@ -87,7 +87,13 @@ public final class TAPConfiguration { /* OUTPUT KEYS */ public final static String KEY_OUTPUT_FORMATS = "output_add_formats"; + public final static String VALUE_ALL = "ALL"; + public final static String VALUE_VOTABLE = "votable"; + public final static String VALUE_VOT = "vot"; + public final static String VALUE_FITS = "fits"; public final static String VALUE_JSON = "json"; + public final static String VALUE_HTML = "html"; + public final static String VALUE_TEXT = "text"; public final static String VALUE_CSV = "csv"; public final static String VALUE_TSV = "tsv"; public final static String VALUE_SV = "sv"; diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html index 0d02f63..f16f3ce 100644 --- a/src/tap/config/tap_configuration_file.html +++ b/src/tap/config/tap_configuration_file.html @@ -113,6 +113,7 @@ <li><u>a URL</u>: here, a redirection toward this URL will be made at each request on the home page</li> <li><u>a classpath</u>: the classpath of an extension of tap.resource.HomePage which must replace the default home page resource. This class MUST have at least one constructor with exactly one parameter not NULL of type tap.resource.TAP.</li> </ul> + <p><em>By default, the default home page provided by the library is used.</em></p> </td> <td><ul><li>my_tap_homepage.jsp</li><li>jsp/my_tap_homepage.jsp</li><li>file:///home/foo/customHomePage.html</li><li>http://...</li><li>{aPackage.NewHomePage}</li></ul></td> </tr> @@ -124,7 +125,7 @@ <p>MIME type of the service home page.</p> <p>This property is used only if the specified "home_page" is a local file path (i.e. if "home_page=file://...").</p> <p>If no value is provided "text/html" will be set by default.</p> - <p><i>Default: <code>text/html</code></i></p> + <p><em>Default: <code>text/html</code></em></p> </td> <td><ul><li>text/html <em>(default)</em></li><li>text/plain</li><li>application/xml</li></ul></td> </tr> @@ -229,7 +230,7 @@ <td> <p><strong>Mandatory if</strong> the password is not already provided in jdbc_url</p> <p>Password used by db_username to access to the database.</p> - <p><em><u>Note:</u> No password encryption can be done in this configuration file for the moment.</em></p> + <p><em><u>Warning:</u> No password encryption can be done in this configuration file for the moment.</em></p> </td> <td></td> </tr> @@ -254,7 +255,7 @@ <td></td> <td>text</td> <td> - <p><b>Mandatory if</b> the value of "metadata" is "xml".</p> + <p><strong>Mandatory if</strong> the value of "metadata" is "xml".</p> <p>Local file path to the TableSet XML document. This XML must implement the schema TableSet defined by <a href="http://www.ivoa.net/xml/VODataService/v1.1">VODataService</a>.</p> </td> <td>/home/foo/my_metadata.xml</td> @@ -286,7 +287,7 @@ <td> <p>Tells whether a directory should be created for each user. If yes, the user directory will be named with the user ID. In this directory, there will be error files, job results and it may be the backup file of the user.</p> - <p><em>The default value is: true.</em></p> + <p><em>Default: <code>true</code></em></p> </td> <td><ul><li>true <i>(default)</i></li><li>false</li></ul></td> </tr> @@ -296,7 +297,7 @@ <td>boolean</td> <td> <p>Tells whether user directories must be grouped. If yes, directories are grouped by the first letter found in the user ID.</p> - <p><em>The default value is: false.</em></p> + <p><em>Default: <code>false</code></em></p> </td> <td><ul><li>true</li><li>false <i>(default)</i></li></ul></td> </tr> @@ -322,7 +323,7 @@ <p>The maximum period MUST be greater or equals to the default retention period. If this rule is not respected, the default retention period is set immediately to the maximum retention period.</p> <p>A negative or null value means there is no restriction over the maximum retention period: the job results will be kept forever. Float values are not allowed.</p> - <p><em>By default query results are kept forever: max_retention_period=0.</em></p></td> + <p><em>Default: <code>max_retention_period=0</code> (results kept for ever)</em></p></td> <td>604800 <em>(1 week)</em></td> </tr> @@ -334,7 +335,7 @@ <td> <p>Frequency at which the UWS service (that's to say, all its users and jobs) must be backuped.</p> <p>Allowed values are: never (no backup will never be done), user_action (each time a user does a writing action, like creating or execution a job), a time (must be positive and not null) in milliseconds.</p> - <p><em>By default, no backup is done, so: backup_frequency=never.</em></p> + <p><em>Default: <code>backup_frequency=never</code> (no backup)</em></p> </td> <td><ul><li>never <em>(default)</em></li><li>user_action</li><li>3600000 <em>(1 hour)</em></li></ul></td> </tr> @@ -345,7 +346,7 @@ <td> <p>Tells whether the backup must be one file for every user, or one file for each user. This second option should be chosen if your TAP Service is organizing its files by user directories ; see the property <em>directory_per_user</em>.</p> <p>Allowed values are: user (one backup file for each user), whole (one file for all users ; may generates a big file).</p> - <p><em>The default mode is: whole.</em></p> + <p><em>Default: <code>whole</code></em></p> </td> <td><ul><li>whole <em>(default)</em></li><li>user</li></ul></td> </tr> @@ -358,7 +359,7 @@ <td> <p>Maximum number of asynchronous jobs that can run simultaneously.</p> <p>A negative or null value means there is no restriction over the number of running asynchronous jobs.</p> - <p><em>By default, there is no restriction: max_async_jobs=0.</em></p> + <p><em>Default: <code>max_async_jobs=0</code> (no restriction)</em></p> </td> <td><ul><li>0 <em>(default)</em></li><li>10</li></ul></td> </tr> @@ -373,7 +374,7 @@ <p>The default duration MUST be less or equals to the maximum execution duration. If this rule is not respected, the default execution duration is set immediately to the maximum execution duration.</p> <p>A negative or null value means there is no restriction over the default execution duration: the execution could never end. Float values are not allowed.</p> - <p><em>By default, there is no restriction: default_execution_duration=0.</em></p> + <p><em>Default: <code>default_execution_duration=0</code> (no restriction)</em></p> </td> <td>600000 <em>(10 minutes)</em></td> </tr> @@ -386,7 +387,7 @@ <p>The maximum duration MUST be greater or equals to the default execution duration. If this rule is not respected, the default execution duration is set immediately to the maximum execution duration.</p> <p>A negative or null value means there is no restriction over the maximum execution duration: the execution could never end. Float values are not allowed.</p> - <p><em>By default, there is no restriction: max_execution_duration=0.</em></p> + <p><em>Default: <code>max_execution_duration=0</code> (no restriction)</em></p> </td> <td>3600000 <em>(1 hour)</em></td> </tr> @@ -397,11 +398,18 @@ <td></td> <td>text</td> <td> - <p>Comma separated list of output formats for query results, in addition to the VOTable.</p> - <p>Allowed values are: json, csv, tsv, sv(<i><separator></i>)[:<i>mime_type</i>[:<i>mime_type_alias</i>]], or a path (within brackets: {...}) to a class implementing OutputFormat<ResultSet> and having at least one constructor with only a tap.ServiceConnection<ResultSet> parameter.</p> - <p><i>Note: if no MIME type or MIME type alias is provided for the sv(...) format, defaults are: </i>text/plain<i> and </i>text<i>. So </i>sv([])<i> is equivalent to </i>sv([]):text/plain:text<i>.</i></p> + <p>Comma separated list of output formats for query results.</p> + <p>Allowed values are: votable (or 'vot'), fits, text, csv, tsv, json and html.</p> + <p>The special value "ALL" will select all formats provided by the library.</p> + <p>The VOTable format may be more detailed with the following syntax: (<code>serialization</code>,<code>version</code>):<code>mime_type</code>:<code>short_mime_type</code>. + The MIME type part and the parameters part may be omitted (e.g. votable:application/xml:votable , votable(td,1.3)]). + Empty string values are allowed for each values (e.g. votable():: , votable(td)::votable).</p> + <p>It is also possible to define a custom Separated Value format, different from CSV and TSV, thanks to the following syntax: sv(<code>separator</code>):<code>mime_type</code>:<code>short_mime_type</code>. + On the contrary to the VOTable syntax, the parameter (i.e. separator) MUST BE provided. The MIME type part may be omitted ; then the MIME type will be set by default to text/plain.</p> + <p>There is finally a last possible value: a classpath to a class implementing OutputFormat and having at least one constructor with exactly one parameter of type tap.ServiceConnection.</p> + <p><em>Default: <code>ALL</code></em></p> </td> - <td><ul><li>json</li><li>csv</li><li>tsv</li><li>sv(|):text/psv:psv</li><li>sv([])</li><li>{apackage.FooOutputFormat}</li></ul></td> + <td><ul><li>votable</li><li>vot</li><li>vot(td,1.2)::votable</li><li>json,html ,csv, text</li><li>sv(|):text/psv:psv</li><li>sv([])</li><li>{apackage.FooOutputFormat}</li></ul></td> </tr> <tr class="optional"> <td class="done">output_default_limit</td> @@ -412,7 +420,7 @@ <p>This limit can be expressed in only one unit: rows.</p> <p>A negative or null value means there is no restriction over this limit. Float values are not allowed.</p> <p>Obviously this limit MUST be less or equal than output_max_limit.</p> - <p><em>By default, there is no restriction: output_default_limit=0</em></p> + <p><em>Default: <code>output_default_limit=0</code> (no restriction)</em></p> </td> <td><ul><li>0 <em>(default)</em></li><li>20</li><li>20r</li><li>20R</li></ul></td> </tr> @@ -425,7 +433,7 @@ <p>This limit can be expressed in only one unit: rows.</p> <p>A negative or null value means there is no restriction over this limit. Float values are not allowed.</p> <p>Obviously this limit MUST be greater or equal than output_default_limit.</p> - <p><em>By default, there is no restriction: output_max_limit=0</em></p> + <p><em>Default: <code>output_max_limit=0</code> (no restriction)</em></p> </td> <td><ul><li>0 <em>(default)</em></li><li>1000</li><li>10000r</li><li>10000R</li></ul></td> </tr> @@ -454,7 +462,7 @@ <p>A negative or null value means there is no restriction over this limit. Float values are not allowed.</p> <p><b>Warning!</b> Obviously this limit MUST be less or equal than upload_max_db_limit, and MUST be of the same type as it. If the chosen type is rows, this limit MUST also be strictly less than upload_max_file_size.</p> - <p><i>By default, there is no restriction: upload_default_db_limit=0</i></p> + <p><em>Default: <code>upload_default_db_limit=0</code> (no restriction)</em></p> </td> <td><ul><li>0 <em>(default)</em></li><li>20</li><li>20r</li><li>20R</li><li>200kB</li></ul></td> </tr> @@ -470,7 +478,7 @@ <p>A negative or null value means there is no restriction over this limit. Float values are not allowed.</p> <p><b>Warning!</b> Obviously this limit MUST be greater or equal than upload_default_db_limit, and MUST be of the same type as it. If the chosen type is rows, this limit MUST also be strictly less than upload_max_file_size.</p> - <p><i>By default, there is no restriction: upload_max_db_limit=0</i></p> + <p><em>Default: <code>upload_max_db_limit=0</code> (no restriction)</em></p> </td> <td><ul><li>0 <em>(default)</em></li><li>10000</li><li>10000r</li><li>10000R</li><li>1MB</li></ul></td> </tr> @@ -484,7 +492,7 @@ Here, unit is case sensitive. No other storage unit is allowed.</p> <p><b>Warning!</b> When the upload is enabled, there must be a maximum file size. Here, no "unlimited" value is possible ; 0 and any negative value are not allowed.</p> <p><b>Warning!</b> In function of the chosen upload_max_db_limit type, upload_max_file_size MUST be greater in order to figure out the file metadata part.</p> - <p><i>By default, the maximum size is set to its maximum possible value: upload_max_file_size=2147483647B (~2GB)</i></p> + <p><em>Default: <code>upload_max_file_size=2147483647B</code> (~2GB ; maximum possible value)</em></p> </td> <td><ul><li>2147483647B <em>(default)</em></li><li>2MB</li></ul></td> </tr> diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties index 0d03a50..b54bb54 100644 --- a/src/tap/config/tap_full.properties +++ b/src/tap/config/tap_full.properties @@ -257,11 +257,25 @@ max_execution_duration = 0 ########## # [OPTIONAL] -# Comma separated list of output formats for query results, in addition to the VOTable. +# Comma separated list of output formats for query results. # -# Allowed values: json, csv, tsv, sv(<separator>), or a path (within brackets: {...}) to a class implementing OutputFormat<ResultSet> -# and having at least one constructor with only a tap.ServiceConnection<ResultSet> parameter. -output_add_formats = +# Allowed values are: votable (or 'vot'), fits, text, csv, tsv, json and html. +# +# The special value "ALL" will select all formats provided by the library. +# +# The VOTable format may be more detailed with the following syntax: (serialization,version):mime_type:short_mime_type. +# The MIME type part and the parameters part may be omitted (e.g. votable:application/xml:votable , votable(td,1.3)]). +# Empty string values are allowed for each values (e.g. votable():: , votable(td)::votable). +# +# It is also possible to define a custom Separated Value format, different from CSV and TSV, thanks to the following syntax: sv(separator):mime_type:short_mime_type. +# On the contrary to the VOTable syntax, the parameter (i.e. separator) MUST BE provided. +# The MIME type part may be omitted ; then the MIME type will be set by default to text/plain. +# +# There is finally a last possible value: a classpath to a class implementing OutputFormat and having at least one constructor with exactly one parameter of type +# tap.ServiceConnection. +# +# Default: ALL +output_add_formats = ALL # [OPTIONAL] # Default limit for the result output. diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java index 07c80e1..2e166d6 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,2014 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS) + * Copyright 2012-2015 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS) * Astronomisches Rechen Institut (ARI) */ @@ -84,7 +84,7 @@ import adql.db.DBType.DBDatatype; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (12/2014) + * @version 2.0 (02/2015) */ public class VOTableFormat implements OutputFormat { @@ -247,19 +247,34 @@ public class VOTableFormat implements OutputFormat { /** * <p>Set the MIME type associated with this format.</p> * - * <p><i>Note: - * Nothing will be done if the given MIME type is NULL. - * But the short form may be NULL. - * </i></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, this function does nothing</i> - * @param shortForm Short form of this MIME type. <i>note: MAY be NULL</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){ - if (mimeType != null){ + if (mimeType != null) this.mimeType = mimeType; + if (shortForm != null) this.shortMimeType = shortForm; - } + } + + /** + * Get the set VOTable data serialization/format (e.g. BINARY, TABLEDATA). + * + * @return The data format. + */ + public final DataFormat getVotSerialization(){ + return votFormat; + } + + /** + * Get the set VOTable version. + * + * @return The VOTable version. + */ + public final VOTableVersion getVotVersion(){ + return votVersion; } @Override diff --git a/test/tap/config/TestConfigurableServiceConnection.java b/test/tap/config/TestConfigurableServiceConnection.java index e4fb170..398b444 100644 --- a/test/tap/config/TestConfigurableServiceConnection.java +++ b/test/tap/config/TestConfigurableServiceConnection.java @@ -1,6 +1,7 @@ package tap.config; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -19,11 +20,14 @@ import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER; import static tap.config.TAPConfiguration.VALUE_ANY; import static tap.config.TAPConfiguration.VALUE_CSV; import static tap.config.TAPConfiguration.VALUE_DB; +import static tap.config.TAPConfiguration.VALUE_FITS; import static tap.config.TAPConfiguration.VALUE_JSON; import static tap.config.TAPConfiguration.VALUE_LOCAL; import static tap.config.TAPConfiguration.VALUE_NONE; import static tap.config.TAPConfiguration.VALUE_SV; +import static tap.config.TAPConfiguration.VALUE_TEXT; import static tap.config.TAPConfiguration.VALUE_TSV; +import static tap.config.TAPConfiguration.VALUE_VOTABLE; import static tap.config.TAPConfiguration.VALUE_XML; import java.io.File; @@ -41,6 +45,10 @@ import org.junit.Test; import tap.ServiceConnection; import tap.ServiceConnection.LimitUnit; import tap.TAPException; +import tap.formatter.OutputFormat; +import tap.formatter.VOTableFormat; +import uk.ac.starlink.votable.DataFormat; +import uk.ac.starlink.votable.VOTableVersion; import uws.UWSException; import uws.job.user.DefaultJobOwner; import uws.job.user.JobOwner; @@ -57,9 +65,11 @@ public class TestConfigurableServiceConnection { private static Properties validProp, noFmProp, fmClassPathProp, incorrectFmProp, xmlMetaProp, missingMetaProp, missingMetaFileProp, wrongMetaProp, wrongMetaFileProp, validFormatsProp, - badSVFormat1Prop, badSVFormat2Prop, unknownFormatProp, - maxAsyncProp, negativeMaxAsyncProp, notIntMaxAsyncProp, - defaultOutputLimitProp, maxOutputLimitProp, + validVOTableFormatsProp, badSVFormat1Prop, badSVFormat2Prop, + badVotFormat1Prop, badVotFormat2Prop, badVotFormat3Prop, + badVotFormat4Prop, badVotFormat5Prop, badVotFormat6Prop, + unknownFormatProp, maxAsyncProp, negativeMaxAsyncProp, + notIntMaxAsyncProp, defaultOutputLimitProp, maxOutputLimitProp, bothOutputLimitGoodProp, bothOutputLimitBadProp, userIdentProp, notClassPathUserIdentProp, geometriesProp, noneGeomProp, anyGeomProp, noneInsideGeomProp, unknownGeomProp, anyUdfsProp, @@ -78,7 +88,7 @@ public class TestConfigurableServiceConnection { noFmProp.setProperty(KEY_FILE_MANAGER, ""); fmClassPathProp = (Properties)validProp.clone(); - fmClassPathProp.setProperty(KEY_FILE_MANAGER, "{tap.config.TestDefaultServiceConnection$FileManagerTest}"); + fmClassPathProp.setProperty(KEY_FILE_MANAGER, "{tap.config.TestConfigurableServiceConnection$FileManagerTest}"); incorrectFmProp = (Properties)validProp.clone(); incorrectFmProp.setProperty(KEY_FILE_MANAGER, "foo"); @@ -102,7 +112,10 @@ public class TestConfigurableServiceConnection { missingMetaFileProp.remove(KEY_METADATA_FILE); validFormatsProp = (Properties)validProp.clone(); - validFormatsProp.setProperty(KEY_OUTPUT_FORMATS, VALUE_JSON + "," + VALUE_CSV + " , " + VALUE_TSV + ",, , " + VALUE_SV + "([])" + ", " + VALUE_SV + "(|):text/psv:psv" + ", " + VALUE_SV + "($)::test" + ", \t " + VALUE_SV + "(@):text/arobase:"); + validFormatsProp.setProperty(KEY_OUTPUT_FORMATS, VALUE_FITS + "," + VALUE_TEXT + "," + VALUE_JSON + "," + VALUE_CSV + " , " + VALUE_TSV + ",, , " + VALUE_SV + "([])" + ", " + VALUE_SV + "(|):text/psv:psv" + ", " + VALUE_SV + "($)::test" + ", \t " + VALUE_SV + "(@):text/arobase:" + ", {tap.formatter.HTMLFormat}"); + + validVOTableFormatsProp = (Properties)validProp.clone(); + validVOTableFormatsProp.setProperty(KEY_OUTPUT_FORMATS, "votable, votable()::, vot(), vot::, votable:, votable(Td, 1.0), vot(TableData), votable(,1.2), vot(Fits):application/fits:supervot"); badSVFormat1Prop = (Properties)validProp.clone(); badSVFormat1Prop.setProperty(KEY_OUTPUT_FORMATS, VALUE_SV); @@ -110,6 +123,24 @@ public class TestConfigurableServiceConnection { badSVFormat2Prop = (Properties)validProp.clone(); badSVFormat2Prop.setProperty(KEY_OUTPUT_FORMATS, VALUE_SV + "()"); + badVotFormat1Prop = (Properties)validProp.clone(); + badVotFormat1Prop.setProperty(KEY_OUTPUT_FORMATS, "votable(foo)"); + + badVotFormat2Prop = (Properties)validProp.clone(); + badVotFormat2Prop.setProperty(KEY_OUTPUT_FORMATS, "vot(,foo)"); + + badVotFormat3Prop = (Properties)validProp.clone(); + badVotFormat3Prop.setProperty(KEY_OUTPUT_FORMATS, "text, vot(TD"); + + badVotFormat4Prop = (Properties)validProp.clone(); + badVotFormat4Prop.setProperty(KEY_OUTPUT_FORMATS, "vot(TD, text"); + + badVotFormat5Prop = (Properties)validProp.clone(); + badVotFormat5Prop.setProperty(KEY_OUTPUT_FORMATS, "vot(TD, 1.0, foo)"); + + badVotFormat6Prop = (Properties)validProp.clone(); + badVotFormat6Prop.setProperty(KEY_OUTPUT_FORMATS, "vot:application/xml:votable:foo"); + unknownFormatProp = (Properties)validProp.clone(); unknownFormatProp.setProperty(KEY_OUTPUT_FORMATS, "foo"); @@ -137,7 +168,7 @@ public class TestConfigurableServiceConnection { bothOutputLimitBadProp.setProperty(KEY_MAX_OUTPUT_LIMIT, "100"); userIdentProp = (Properties)validProp.clone(); - userIdentProp.setProperty(KEY_USER_IDENTIFIER, "{tap.config.TestDefaultServiceConnection$UserIdentifierTest}"); + userIdentProp.setProperty(KEY_USER_IDENTIFIER, "{tap.config.TestConfigurableServiceConnection$UserIdentifierTest}"); notClassPathUserIdentProp = (Properties)validProp.clone(); notClassPathUserIdentProp.setProperty(KEY_USER_IDENTIFIER, "foo"); @@ -228,7 +259,7 @@ public class TestConfigurableServiceConnection { assertNotNull(connection.getTAPMetadata()); assertTrue(connection.getTAPMetadata().getNbSchemas() >= 1); assertTrue(connection.getTAPMetadata().getNbTables() >= 5); - assertTrue(connection.isAvailable()); + assertFalse(connection.isAvailable()); assertEquals(DEFAULT_MAX_ASYNC_JOBS, connection.getNbMaxAsyncJobs()); assertTrue(connection.getRetentionPeriod()[0] <= connection.getRetentionPeriod()[1]); assertTrue(connection.getExecutionDuration()[0] <= connection.getExecutionDuration()[1]); @@ -258,7 +289,7 @@ public class TestConfigurableServiceConnection { assertNotNull(connection.getTAPMetadata()); assertEquals(nbSchemas, connection.getTAPMetadata().getNbSchemas()); assertEquals(nbTables, connection.getTAPMetadata().getNbTables()); - assertTrue(connection.isAvailable()); + assertFalse(connection.isAvailable()); assertEquals(DEFAULT_MAX_ASYNC_JOBS, connection.getNbMaxAsyncJobs()); assertTrue(connection.getRetentionPeriod()[0] <= connection.getRetentionPeriod()[1]); assertTrue(connection.getExecutionDuration()[0] <= connection.getExecutionDuration()[1]); @@ -321,7 +352,7 @@ public class TestConfigurableServiceConnection { assertNotNull(connection.getFileManager()); assertNotNull(connection.getFactory()); assertNotNull(connection.getTAPMetadata()); - assertTrue(connection.isAvailable()); + assertFalse(connection.isAvailable()); /* Retention periods and execution durations are different in this configuration file from the valid one (validProp). * Max period and max duration are set in this file as less than respectively the default period and the default duration. @@ -346,6 +377,7 @@ public class TestConfigurableServiceConnection { // Valid output formats list: try{ ServiceConnection connection = new ConfigurableServiceConnection(validFormatsProp); + assertNotNull(connection.getOutputFormat(VALUE_VOTABLE)); assertNotNull(connection.getOutputFormat(VALUE_JSON)); assertNotNull(connection.getOutputFormat(VALUE_CSV)); assertNotNull(connection.getOutputFormat(VALUE_TSV)); @@ -359,6 +391,69 @@ public class TestConfigurableServiceConnection { fail("This MUST have succeeded because the property file is valid! \nCaught exception: " + getPertinentMessage(e)); } + // Valid VOTable output formats list: + try{ + ServiceConnection connection = new ConfigurableServiceConnection(validVOTableFormatsProp); + Iterator<OutputFormat> it = connection.getOutputFormats(); + OutputFormat f = it.next(); /* votable */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml", f.getMimeType()); + assertEquals(VALUE_VOTABLE, f.getShortMimeType()); + assertEquals(DataFormat.BINARY, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V13, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* votable():: */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml", f.getMimeType()); + assertEquals(VALUE_VOTABLE, f.getShortMimeType()); + assertEquals(DataFormat.BINARY, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V13, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* vot() */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml", f.getMimeType()); + assertEquals(VALUE_VOTABLE, f.getShortMimeType()); + assertEquals(DataFormat.BINARY, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V13, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* vot:: */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml", f.getMimeType()); + assertEquals(VALUE_VOTABLE, f.getShortMimeType()); + assertEquals(DataFormat.BINARY, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V13, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* votable: */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml", f.getMimeType()); + assertEquals(VALUE_VOTABLE, f.getShortMimeType()); + assertEquals(DataFormat.BINARY, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V13, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* votable(Td, 1.0) */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml;serialization=TABLEDATA", f.getMimeType()); + assertEquals("votable/td", f.getShortMimeType()); + assertEquals(DataFormat.TABLEDATA, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V10, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* votable(TableData) */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml;serialization=TABLEDATA", f.getMimeType()); + assertEquals("votable/td", f.getShortMimeType()); + assertEquals(DataFormat.TABLEDATA, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V13, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* votable(, 1.2) */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/x-votable+xml", f.getMimeType()); + assertEquals(VALUE_VOTABLE, f.getShortMimeType()); + assertEquals(DataFormat.BINARY, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V12, ((VOTableFormat)f).getVotVersion()); + f = it.next(); /* vot(fits):application/fits,supervot */ + assertEquals(VOTableFormat.class, f.getClass()); + assertEquals("application/fits", f.getMimeType()); + assertEquals("supervot", f.getShortMimeType()); + assertEquals(DataFormat.FITS, ((VOTableFormat)f).getVotSerialization()); + assertEquals(VOTableVersion.V13, ((VOTableFormat)f).getVotVersion()); + assertFalse(it.hasNext()); + }catch(Exception e){ + fail("This MUST have succeeded because the property file is valid! \nCaught exception: " + getPertinentMessage(e)); + } + // Bad SV(...) format 1 = "sv": try{ new ConfigurableServiceConnection(badSVFormat1Prop); @@ -377,6 +472,60 @@ public class TestConfigurableServiceConnection { assertEquals("Missing separator char/string for the SV output format: \"sv()\"!", e.getMessage()); } + // Bad VOTable(...) format 1 = "votable(foo)": + try{ + new ConfigurableServiceConnection(badVotFormat1Prop); + fail("This MUST have failed because an incorrect VOTable output format value has been provided!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Unsupported VOTable serialization: \"foo\"! Accepted values: 'binary' (or 'b'), 'binary2' (or 'b2'), 'tabledata' (or 'td') and 'fits'.", e.getMessage()); + } + + // Bad VOTable(...) format 2 = "votable(,foo)": + try{ + new ConfigurableServiceConnection(badVotFormat2Prop); + fail("This MUST have failed because an incorrect VOTable output format value has been provided!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Unsupported VOTable version: \"foo\"! Accepted values: '1.0' (or 'v1.0'), '1.1' (or 'v1.1'), '1.2' (or 'v1.2') and '1.3' (or 'v1.3').", e.getMessage()); + } + + // Bad VOTable(...) format 3 = "text, vot(TD": + try{ + new ConfigurableServiceConnection(badVotFormat3Prop); + fail("This MUST have failed because an incorrect VOTable output format value has been provided!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Wrong output format specification syntax in: \"vot(TD\"! A VOTable parameters list must end with ')'.", e.getMessage()); + } + + // Bad VOTable(...) format 4 = "vot(TD, text": + try{ + new ConfigurableServiceConnection(badVotFormat4Prop); + fail("This MUST have failed because an incorrect VOTable output format value has been provided!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Missing right parenthesis in: \"vot(TD, text\"!", e.getMessage()); + } + + // Bad VOTable(...) format 5 = "vot(TD, 1.0, foo)": + try{ + new ConfigurableServiceConnection(badVotFormat5Prop); + fail("This MUST have failed because an incorrect VOTable output format value has been provided!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Wrong number of parameters for the output format VOTable: \"vot(TD, 1.0, foo)\"! Only two parameters may be provided: serialization and version.", e.getMessage()); + } + + // Bad VOTable(...) format 6 = "vot:application/xml:votable:foo": + try{ + new ConfigurableServiceConnection(badVotFormat6Prop); + fail("This MUST have failed because an incorrect VOTable output format value has been provided!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Wrong output format specification syntax in: \"vot:application/xml:votable:foo\"! After a MIME type and a short MIME type, no more information is expected.", e.getMessage()); + } + // Unknown output format: try{ new ConfigurableServiceConnection(unknownFormatProp); -- GitLab