diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java index 20712904bb788ce919042e9e81288da804c572c7..f624992b6f272ee03d7d971acfbfeec20495f7a2 100644 --- a/src/tap/config/ConfigurableServiceConnection.java +++ b/src/tap/config/ConfigurableServiceConnection.java @@ -2,21 +2,21 @@ package tap.config; /* * 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 2016-2017 - Astronomisches Rechen Institut (ARI) + * + * Copyright 2016-2018 - Astronomisches Rechen Institut (ARI) */ import static tap.config.TAPConfiguration.DEFAULT_ASYNC_FETCH_SIZE; @@ -89,6 +89,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import adql.db.FunctionDef; import adql.db.STCS; @@ -120,14 +122,14 @@ import uws.service.log.UWSLog.LogLevel; /** * <p>Concrete implementation of {@link ServiceConnection}, fully parameterized with a TAP configuration file.</p> - * + * * <p> * Every aspects of the TAP service are configured here. This instance is also creating the {@link TAPFactory} using the * TAP configuration file thanks to the implementation {@link ConfigurableTAPFactory}. * </p> - * + * * @author Grégory Mantelet (ARI) - * @version 2.1 (09/2017) + * @version 2.1 (02/2018) * @since 2.0 */ public final class ConfigurableServiceConnection implements ServiceConnection { @@ -206,9 +208,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Create a TAP service description thanks to the given TAP configuration file. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws NullPointerException If the given properties set is NULL. * @throws TAPException If a property is wrong or missing. */ @@ -218,12 +220,12 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Create a TAP service description thanks to the given TAP configuration file. - * + * * @param tapConfig The content of the TAP configuration file. * @param webAppRootDir The directory of the Web Application running this TAP service. * <em>In this directory another directory may be created in order to store all TAP service files * if none is specified in the given TAP configuration file.</em> - * + * * @throws NullPointerException If the given properties set is NULL. * @throws TAPException If a property is wrong or missing. */ @@ -279,12 +281,12 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the management of TAP service files using the given TAP configuration file. - * + * * @param tapConfig The content of the TAP configuration file. * @param webAppRootDir The directory of the Web Application running this TAP service. * <em>This directory may be used only to search the root TAP directory * if specified with a relative path in the TAP configuration file.</em> - * + * * @throws TAPException If a property is wrong or missing, or if an error occurs while creating the file manager. */ private void initFileManager(final Properties tapConfig, final String webAppRootDir) throws TAPException{ @@ -325,16 +327,16 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Resolve the given file name/path.</p> - * + * * <p> * If not an absolute path, the given path may be either relative or absolute. A relative path is always considered * as relative from the Web Application directory (supposed to be given in 2nd parameter). * </p> - * + * * @param filePath Path/Name of the file to get. * @param webAppRootPath Web Application directory local path. * @param propertyName Name of the property which gives the given file path. - * + * * @return The specified File instance. * * @throws ParseException If the given file path is a URI/URL. @@ -354,9 +356,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the TAP logger with the given TAP configuration file. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If no instance of the specified custom logger can * be created. */ @@ -394,16 +396,16 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Initialize the {@link TAPFactory} to use.</p> - * + * * <p> * The built factory is either a {@link ConfigurableTAPFactory} instance (by default) or * an instance of the class specified in the TAP configuration file. * </p> - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If an error occurs while building the specified {@link TAPFactory}. - * + * * @see ConfigurableTAPFactory */ private void initFactory(final Properties tapConfig) throws TAPException{ @@ -418,16 +420,16 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the TAP metadata (i.e. database schemas, tables and columns and their attached metadata). - * + * * @param tapConfig The content of the TAP configuration file. * @param webAppRootDir Web Application directory local path. * <em>This directory may be used if a relative path is given for an XML metadata file.</em> - * + * * @return The extracted TAP metadata. - * + * * @throws TAPException If some TAP configuration file properties are wrong or missing, * or if an error has occurred while extracting the metadata from the database or the XML file. - * + * * @see DBConnection#getTAPSchema() * @see TableSetParser */ @@ -592,9 +594,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the maximum number of asynchronous jobs. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration property is wrong. */ private void initMaxAsyncJobs(final Properties tapConfig) throws TAPException{ @@ -610,9 +612,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the default and maximum retention period. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initRetentionPeriod(final Properties tapConfig) throws TAPException{ @@ -642,9 +644,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the default and maximum execution duration. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initExecutionDuration(final Properties tapConfig) throws TAPException{ @@ -674,15 +676,15 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Initialize the list of all output format that the TAP service must support.</p> - * + * * <p> * This function ensures that at least one VOTable format is part of the returned list, * even if none has been specified in the TAP configuration file. Indeed, the VOTable format is the only * format required for a TAP service. * </p> - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void addOutputFormats(final Properties tapConfig) throws TAPException{ @@ -805,13 +807,13 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Parse the given VOTable format specification.</p> - * + * * <p>This specification is expected to be an item of the property {@link TAPConfiguration#KEY_OUTPUT_FORMATS}.</p> - * + * * @param propValue A single VOTable format specification. - * + * * @return The corresponding configured {@link VOTableFormat} instance. - * + * * @throws TAPException If the syntax of the given specification is incorrect, * or if the specified VOTable version or serialization does not exist. */ @@ -893,9 +895,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the default and maximum output limits. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initOutputLimits(final Properties tapConfig) throws TAPException{ @@ -910,9 +912,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the fetch size for the synchronous and for the asynchronous resources. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initFetchSize(final Properties tapConfig) throws TAPException{ @@ -949,9 +951,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the default and maximum upload limits. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initUploadLimits(final Properties tapConfig) throws TAPException{ @@ -969,9 +971,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the maximum size (in bytes) of a VOTable files set upload. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration property is wrong. */ private void initMaxUploadSize(final Properties tapConfig) throws TAPException{ @@ -993,9 +995,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the TAP user identification method. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration property is wrong. */ private void initUserIdentifier(final Properties tapConfig) throws TAPException{ @@ -1007,9 +1009,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the list of all allowed coordinate systems. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initCoordSys(final Properties tapConfig) throws TAPException{ @@ -1065,9 +1067,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Initialize the list of all allowed ADQL geometrical functions. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initADQLGeometries(final Properties tapConfig) throws TAPException{ @@ -1118,11 +1120,25 @@ public final class ConfigurableServiceConnection implements ServiceConnection { } } + private final String REGEXP_SIGNATURE = "(\\([^()]*\\)|[^,])*"; + + private final String REGEXP_CLASSPATH = "\\{[^{}]*\\}"; + + private final String REGEXP_DESCRIPTION = "\"((\\\"|[^\"])*)\""; + + private final String REGEXP_UDF = "\\[\\s*(" + REGEXP_SIGNATURE + ")\\s*(,\\s*(" + REGEXP_CLASSPATH + ")?\\s*(,\\s*(" + REGEXP_DESCRIPTION + ")?\\s*)?)?\\]"; + + private final String REGEXP_UDFS = "\\s*(" + REGEXP_UDF + ")\\s*(,(.*))?"; + private final int GROUP_SIGNATURE = 2; + private final int GROUP_CLASSPATH = 5; + private final int GROUP_DESCRIPTION = 8; + private final int GROUP_NEXT_UDFs = 11; + /** * Initialize the list of all known and allowed User Defined Functions. - * + * * @param tapConfig The content of the TAP configuration file. - * + * * @throws TAPException If the corresponding TAP configuration properties are wrong. */ private void initUDFs(final Properties tapConfig) throws TAPException{ @@ -1130,7 +1146,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection { String propValue = getProperty(tapConfig, KEY_UDFS); // NO VALUE => NO UNKNOWN FCT ALLOWED! - if (propValue == null) + if (propValue == null || propValue.trim().length() == 0) udfs = new ArrayList<FunctionDef>(0); // "NONE" => NO UNKNOWN FCT ALLOWED (= none of the unknown functions are allowed)! @@ -1144,139 +1160,69 @@ public final class ConfigurableServiceConnection implements ServiceConnection { // OTHERWISE, JUST THE ALLOWED ONE ARE LISTED: else{ - char c; - int ind = 0; - short nbComma = 0; - boolean within_item = false, within_params = false, - within_classpath = false; - StringBuffer buf = new StringBuffer(); - String signature, classpath; - int[] posSignature = new int[]{-1,-1}, - posClassPath = new int[]{-1,-1}; - - signature = null; - classpath = null; - buf.delete(0, buf.length()); - - while(ind < propValue.length()){ - // Get the character: - c = propValue.charAt(ind++); - // If space => ignore - if (!within_params && Character.isWhitespace(c)) - continue; - // If inside a parameters list, keep all characters until the list end (')'): - if (within_params){ - if (c == ')') - within_params = false; - buf.append(c); - } - // If inside a classpath, keep all characters until the classpath end ('}'): - else if (within_classpath){ - if (c == '}') - within_classpath = false; - buf.append(c); - } - // If inside an UDF declaration: - else if (within_item){ - switch(c){ - case '(': /* start of a parameters list */ - within_params = true; - buf.append(c); - break; - case '{': /* start of a class name */ - within_classpath = true; - buf.append(c); - break; - case ',': /* separation between the signature and the class name */ - // count commas within this item: - if (++nbComma > 1) - // if more than 1, throw an error: - throw new TAPException("Wrong UDF declaration syntax: only two items (signature and class name) can be given within brackets. (position in the property " + KEY_UDFS + ": " + ind + ")"); - else{ - // end of the signature and start of the class name: - signature = buf.toString(); - buf.delete(0, buf.length()); - posSignature[1] = ind; - posClassPath[0] = ind + 1; - } - break; - case ']': /* end of a UDF declaration */ - within_item = false; - if (nbComma == 0){ - signature = buf.toString(); - posSignature[1] = ind; - }else{ - classpath = (buf.length() == 0 ? null : buf.toString()); - if (classpath != null) - posClassPath[1] = ind; - } - buf.delete(0, buf.length()); - - // no signature... - if (signature == null || signature.length() == 0){ - // ...BUT a class name => error - if (classpath != null) - throw new TAPException("Missing UDF declaration! (position in the property " + KEY_UDFS + ": " + posSignature[0] + "-" + posSignature[1] + ")"); - // ... => ignore this item - else - continue; - } + Pattern patternUDFS = Pattern.compile(REGEXP_UDFS); + String udfList = propValue; + int udfOffset = 1; + while(udfList != null){ + Matcher matcher = patternUDFS.matcher(udfList); + if (matcher.matches()){ + + // Fetch the signature, classpath and description: + String signature = matcher.group(GROUP_SIGNATURE), + classpath = matcher.group(GROUP_CLASSPATH), + description = matcher.group(GROUP_DESCRIPTION); + + // If no signature... + boolean ignoreUdf = false; + if (signature == null || signature.length() == 0){ + // ...BUT a class name => error + if (classpath != null) + throw new TAPException("Missing UDF declaration! (position in the property " + KEY_UDFS + ": " + (udfOffset + matcher.start(GROUP_SIGNATURE)) + "-" + (udfOffset + matcher.end(GROUP_SIGNATURE)) + ")"); + // ... => ignore this item + else + ignoreUdf = true; + } - // add the new UDF in the list: - try{ - // resolve the function signature: - FunctionDef def = FunctionDef.parse(signature); - // resolve the class name: - if (classpath != null){ - if (isClassName(classpath)){ - Class<? extends UserDefinedFunction> fctClass = null; - try{ - // fetch the class: - fctClass = fetchClass(classpath, KEY_UDFS, UserDefinedFunction.class); - // set the class inside the UDF definition: - def.setUDFClass(fctClass); - }catch(TAPException te){ - throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": " + te.getMessage() + " (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")", te); - }catch(IllegalArgumentException iae){ - throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": missing a constructor with a single parameter of type ADQLOperand[] " + (fctClass != null ? "in the class \"" + fctClass.getName() + "\"" : "") + "! (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")"); - } - }else - throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": \"" + classpath + "\" is not a class name (or is not surrounding by {} as expected in this property file)! (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")"); - } - // add the UDF: - udfs.add(def); - }catch(ParseException pe){ - throw new TAPException("Wrong UDF declaration syntax: " + pe.getMessage() + " (position in the property " + KEY_UDFS + ": " + posSignature[0] + "-" + posSignature[1] + ")", pe); + if (!ignoreUdf){ + // Add the new UDF in the list: + try{ + // resolve the function signature: + FunctionDef def = FunctionDef.parse(signature); + // resolve the class name: + if (classpath != null){ + if (isClassName(classpath)){ + Class<? extends UserDefinedFunction> fctClass = null; + try{ + // fetch the class: + fctClass = fetchClass(classpath, KEY_UDFS, UserDefinedFunction.class); + // set the class inside the UDF definition: + def.setUDFClass(fctClass); + }catch(TAPException te){ + throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": " + te.getMessage() + " (position in the property " + KEY_UDFS + ": " + (udfOffset + matcher.start(GROUP_CLASSPATH)) + "-" + (udfOffset + matcher.end(GROUP_CLASSPATH)) + ")", te); + }catch(IllegalArgumentException iae){ + throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": missing a constructor with a single parameter of type ADQLOperand[] " + (fctClass != null ? "in the class \"" + fctClass.getName() + "\"" : "") + "! (position in the property " + KEY_UDFS + ": " + (udfOffset + matcher.start(GROUP_CLASSPATH)) + "-" + (udfOffset + matcher.end(GROUP_CLASSPATH)) + ")"); + } + }else + throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": \"" + classpath + "\" is not a class name (or is not surrounding by {} as expected in this property file)! (position in the property " + KEY_UDFS + ": " + (udfOffset + matcher.start(GROUP_CLASSPATH)) + "-" + (udfOffset + matcher.end(GROUP_CLASSPATH)) + ")"); } - - // reset some variables: - nbComma = 0; - signature = null; - classpath = null; - break; - default: /* keep all other characters */ - buf.append(c); - break; - } - } - // If outside of everything, just starting a UDF declaration or separate each declaration is allowed: - else{ - switch(c){ - case '[': - within_item = true; - posSignature[0] = ind + 1; - break; - case ',': - break; - default: - throw new TAPException("Wrong UDF declaration syntax: unexpected character at position " + ind + " in the property " + KEY_UDFS + ": \"" + c + "\"! A UDF declaration must have one of the following syntaxes: \"[signature]\" or \"[signature,{className}]\"."); + // set the description if any: + if (description != null) + def.description = description; + // add the UDF: + udfs.add(def); + }catch(ParseException pe){ + throw new TAPException("Wrong UDF declaration syntax: " + pe.getMessage() + " (position in the property " + KEY_UDFS + ": " + (udfOffset + matcher.start(GROUP_SIGNATURE)) + "-" + (udfOffset + matcher.end(GROUP_SIGNATURE)) + ")", pe); + } } - } - } - // If the parsing is not finished, throw an error: - if (within_item) - throw new TAPException("Wrong UDF declaration syntax: missing closing bracket at position " + propValue.length() + "!"); + // Prepare the next iteration (i.e. the other UDFs): + udfList = matcher.group(GROUP_NEXT_UDFs); + if (udfList != null && udfList.trim().length() == 0) + udfList = null; + udfOffset += matcher.start(GROUP_NEXT_UDFs); + }else + throw new TAPException("Wrong UDF declaration syntax: \"" + udfList + "\"! (position in the property " + KEY_UDFS + ": " + udfOffset + "-" + (propValue.length() + 1) + ")"); + } } } @@ -1313,16 +1259,16 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the default retention period.</p> - * + * * <p>This period is set by default if the user did not specify one before the execution of his query.</p> - * + * * <p><em><b>Important note:</b> * This function will apply the given retention period only if legal compared to the currently set maximum value. * In other words, if the given value is less or equals to the current maximum retention period. * </em></p> - * + * * @param period New default retention period (in seconds). - * + * * @return <i>true</i> if the given retention period has been successfully set, <i>false</i> otherwise. */ public boolean setDefaultRetentionPeriod(final int period){ @@ -1335,15 +1281,15 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the maximum retention period.</p> - * + * * <p>This period limits the default retention period and the retention period specified by a user.</p> - * + * * <p><em><b>Important note:</b> * This function may reduce the default retention period if the current default retention period is bigger * to the new maximum retention period. In a such case, the default retention period is set to the * new maximum retention period. * </em></p> - * + * * @param period New maximum retention period (in seconds). */ public void setMaxRetentionPeriod(final int period){ @@ -1361,16 +1307,16 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the default execution duration.</p> - * + * * <p>This duration is set by default if the user did not specify one before the execution of his query.</p> - * + * * <p><em><b>Important note:</b> * This function will apply the given execution duration only if legal compared to the currently set maximum value. * In other words, if the given value is less or equals to the current maximum execution duration. * </em></p> - * + * * @param duration New default execution duration (in milliseconds). - * + * * @return <i>true</i> if the given execution duration has been successfully set, <i>false</i> otherwise. */ public boolean setDefaultExecutionDuration(final int duration){ @@ -1383,15 +1329,15 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the maximum execution duration.</p> - * + * * <p>This duration limits the default execution duration and the execution duration specified by a user.</p> - * + * * <p><em><b>Important note:</b> * This function may reduce the default execution duration if the current default execution duration is bigger * to the new maximum execution duration. In a such case, the default execution duration is set to the * new maximum execution duration. * </em></p> - * + * * @param duration New maximum execution duration (in milliseconds). */ public void setMaxExecutionDuration(final int duration){ @@ -1421,12 +1367,12 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Add the given {@link OutputFormat} in the list of output formats supported by the TAP service.</p> - * + * * <p><b>Warning: * No verification is done in order to avoid duplicated output formats in the list. * NULL objects are merely ignored silently. * </b></p> - * + * * @param newOutputFormat New output format. */ public void addOutputFormat(final OutputFormat newOutputFormat){ @@ -1436,9 +1382,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Remove the specified output format. - * + * * @param mimeOrAlias Full or short MIME type of the output format to remove. - * + * * @return <i>true</i> if the specified format has been found and successfully removed from the list, * <i>false</i> otherwise. */ @@ -1457,16 +1403,16 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the default output limit.</p> - * + * * <p>This limit is set by default if the user did not specify one before the execution of his query.</p> - * + * * <p><em><b>Important note:</b> * This function will apply the given output limit only if legal compared to the currently set maximum value. * In other words, if the given value is less or equals to the current maximum output limit. * </em></p> - * + * * @param limit New default output limit (in number of rows). - * + * * @return <i>true</i> if the given output limit has been successfully set, <i>false</i> otherwise. */ public boolean setDefaultOutputLimit(final int limit){ @@ -1479,15 +1425,15 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the maximum output limit.</p> - * + * * <p>This output limit limits the default output limit and the output limit specified by a user.</p> - * + * * <p><em><b>Important note:</b> * This function may reduce the default output limit if the current default output limit is bigger * to the new maximum output limit. In a such case, the default output limit is set to the * new maximum output limit. * </em></p> - * + * * @param limit New maximum output limit (in number of rows). */ public void setMaxOutputLimit(final int limit){ @@ -1544,7 +1490,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * Set the unit of the upload limit. - * + * * @param type Unit of upload limit (rows or bytes). */ public void setUploadLimitType(final LimitUnit type){ @@ -1554,14 +1500,14 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the default upload limit.</p> - * + * * <p><em><b>Important note:</b> * This function will apply the given upload limit only if legal compared to the currently set maximum value. * In other words, if the given value is less or equals to the current maximum upload limit. * </em></p> - * + * * @param limit New default upload limit. - * + * * @return <i>true</i> if the given upload limit has been successfully set, <i>false</i> otherwise. */ public boolean setDefaultUploadLimit(final int limit){ @@ -1576,15 +1522,15 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the maximum upload limit.</p> - * + * * <p>This upload limit limits the default upload limit.</p> - * + * * <p><em><b>Important note:</b> * This function may reduce the default upload limit if the current default upload limit is bigger * to the new maximum upload limit. In a such case, the default upload limit is set to the * new maximum upload limit. * </em></p> - * + * * @param limit New maximum upload limit. */ public void setMaxUploadLimit(final int limit){ @@ -1604,16 +1550,16 @@ public final class ConfigurableServiceConnection implements ServiceConnection { /** * <p>Set the maximum size of a VOTable files set that can be uploaded in once.</p> - * + * * <p><b>Warning: * This size can not be negative or 0. If the given value is in this case, nothing will be done * and <i>false</i> will be returned. * On the contrary to the other limits, no "unlimited" limit is possible here ; only the * maximum value can be set (i.e. maximum positive integer value). * </b></p> - * + * * @param maxSize New maximum size (in bytes). - * + * * @return <i>true</i> if the size has been successfully set, <i>false</i> otherwise. */ public boolean setMaxUploadSize(final int maxSize){ diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html index 122097d893de8a1d6871c3edab5bbcfb9e7400dd..2345960b44d593f1ff9640bf71a00bec611ff6c4 100644 --- a/src/tap/config/tap_configuration_file.html +++ b/src/tap/config/tap_configuration_file.html @@ -718,11 +718,18 @@ <td> <p>Comma-separated list of all allowed UDFs (User Defined Functions).</p> <p> - Each item of the list must have the following syntax: <code>[fct_signature]</code> or <code>[fct_signature, className]</code>. - <i>fct_function</i> is the function signature. Its syntax is the same as in <a href="http://www.ivoa.net/documents/TAPRegExt/20120827/REC-TAPRegExt-1.0.html#langs">TAPRegExt</a>. - <i>className</i> is the name of a class extending UserDefinedFunction. An instance of this class will replace any reference of a UDF - written in an ADQL function with the associated signature. A class name must be specified if the function to represent has a signature - (and more particularly a name) different in ADQL and in SQL. + + Each item of the list must have the following syntax: <code>[fct_signature]</code>, + <code>[fct_signature, className]</code> or <code>[fct_signature, className, description]</code>. + <i>fct_function</i> is the function signature. Its syntax is the same as in + <a href="http://www.ivoa.net/documents/TAPRegExt/20120827/REC-TAPRegExt-1.0.html#langs">TAPRegExt</a>. + <i>className</i> is the name of a class extending UserDefinedFunction. + An instance of this class will replace any reference of a UDF written in an + ADQL function with the associated signature. A class name must be specified if + the function to represent has a signature (and more particularly a name) + different in ADQL and in SQL. <i>description</i> is the human description of the + function to be displayed in the <i>/capabilities</i> of the TAP service. It must be + written between double quotes. </p> <p> If the list is empty (no item), all unknown functions are forbidden. And if the special value <code>ANY</code> is given, any unknown function is allowed ; @@ -730,7 +737,13 @@ </p> <p><em>By default, no unknown function is allowed.</em></p> </td> - <td><ul><li>ø <em>(default)</em></li><li>ANY</li><li>[trim(txt String) -> String], [random() -> DOUBLE]</li><li>[newFct(x double)->double, {apackage.MyNewFunction}]</li></ul></td> + <td><ul><li>ø <em>(default)</em></li> + <li>ANY</li> + <li>[trim(txt String) -> String], [random() -> DOUBLE]</li> + <li>[newFct(x double)->double, {apackage.MyNewFunction}]</li> + <li>[ivo_healpix_index(hpxOrder integer, ra double, dec double) -> bigint, {adql.query.operand.function.healpix.HealpixIndex}, "Compute the index of the \"Healpix cell\" containing the specified position at the given Healpix order."]</li> + <li>[random() -> DOUBLE,,"Generate a random number."]</li> + </ul></td> </tr> <tr><td colspan="5">Additional TAP Resources</td></tr> diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties index 0fbf1deafe7867ce2bdee67fc08be79ef8323b9a..dce6ffcbc135d1a21cf82224d6cc2dbea6222a9a 100644 --- a/src/tap/config/tap_full.properties +++ b/src/tap/config/tap_full.properties @@ -2,7 +2,7 @@ # FULL TAP CONFIGURATION FILE # # # # TAP Version: 2.1 # -# Date: 11 Feb. 2018 # +# Date: 26 Feb. 2018 # # Author: Gregory Mantelet (ARI) # # # ################################################################################ @@ -663,13 +663,24 @@ geometries = # [OPTIONAL] # Comma-separated list of all allowed UDFs (User Defined Functions). # -# Each item of the list must have the following syntax: [fct_signature] or -# [fct_signature, className]. fct_function is the function signature. Its syntax -# is the same as in TAPRegExt. className is the name of a class extending -# UserDefinedFunction. An instance of this class will replace any reference of a -# UDF written in an ADQL function with the associated signature. A class name -# must be specified if the function to represent has a signature (and more -# particularly a name) different in ADQL and in SQL. +# Each item of the list must have the following syntax: [fct_signature], +# [fct_signature, className] or [fct_signature, className, description]. +# fct_function is the function signature. Its syntax is the same as in +# TAPRegExt. className is the name of a class extending UserDefinedFunction. +# An instance of this class will replace any reference of a UDF written in an +# ADQL function with the associated signature. A class name must be specified if +# the function to represent has a signature (and more particularly a name) +# different in ADQL and in SQL. description is the human description of the +# function to be displayed in the /capabilities of the TAP service. It must be +# written between double quotes. +# +# Example: udfs = [ivo_healpix_index(hpxOrder integer, ra double, dec double) +# -> bigint, {adql.query.operand.function.healpix.HealpixIndex} +# , "Compute the index of the \"Healpix cell\" containing the +# specified position at the given Healpix order."], +# [trim(txt String) -> String], +# [newFct(x double)->double, {apackage.MyNewFunction}], +# [random() -> DOUBLE,,"Generate a random number."] # # If the list is empty (no item), all unknown functions are forbidden. And if # the special value ANY is given, any unknown function is allowed ; consequently diff --git a/test/tap/config/TestConfigurableServiceConnection.java b/test/tap/config/TestConfigurableServiceConnection.java index e0e3be40a32aff8d84f8c6d42067270b7ab1fc45..45a23904db34bc4c3267ec030b756bfd968cceaf 100644 --- a/test/tap/config/TestConfigurableServiceConnection.java +++ b/test/tap/config/TestConfigurableServiceConnection.java @@ -108,10 +108,12 @@ public class TestConfigurableServiceConnection { anyCoordSysProp, noneInsideCoordSysProp, unknownCoordSysProp, geometriesProp, noneGeomProp, anyGeomProp, noneInsideGeomProp, unknownGeomProp, anyUdfsProp, noneUdfsProp, udfsProp, - udfsWithClassNameProp, udfsListWithNONEorANYProp, - udfsWithWrongParamLengthProp, udfsWithMissingBracketsProp, - udfsWithMissingDefProp1, udfsWithMissingDefProp2, emptyUdfItemProp1, - emptyUdfItemProp2, udfWithMissingEndBracketProp, customFactoryProp, + udfsWithClassNameProp, udfsWithClassNameAndDescriptionProp, + udfsWithEmptyOptParamsProp, udfsListWithNONEorANYProp, + udfsWithWrongDescriptionFormatProp, udfsWithWrongParamLengthProp, + udfsWithMissingBracketsProp, udfsWithMissingDefProp1, + udfsWithMissingDefProp2, emptyUdfItemProp1, emptyUdfItemProp2, + udfWithMissingEndBracketProp, customFactoryProp, customConfigurableFactoryProp, badCustomFactoryProp; @BeforeClass @@ -305,11 +307,20 @@ public class TestConfigurableServiceConnection { udfsWithClassNameProp = (Properties)validProp.clone(); udfsWithClassNameProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}]"); + udfsWithClassNameAndDescriptionProp = (Properties)validProp.clone(); + udfsWithClassNameAndDescriptionProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}, \"Bla \"bla\".\"]"); + + udfsWithEmptyOptParamsProp = (Properties)validProp.clone(); + udfsWithEmptyOptParamsProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR,, ]"); + udfsListWithNONEorANYProp = (Properties)validProp.clone(); udfsListWithNONEorANYProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR],ANY"); + udfsWithWrongDescriptionFormatProp = (Properties)validProp.clone(); + udfsWithWrongDescriptionFormatProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}, Blabla]"); + udfsWithWrongParamLengthProp = (Properties)validProp.clone(); - udfsWithWrongParamLengthProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}, foo]"); + udfsWithWrongParamLengthProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}, \"Blabla\", foo]"); udfsWithMissingBracketsProp = (Properties)validProp.clone(); udfsWithMissingBracketsProp.setProperty(KEY_UDFS, "toto(a string)->VARCHAR"); @@ -348,18 +359,18 @@ public class TestConfigurableServiceConnection { * CONSTRUCTOR TESTS * * In general: * - A valid configuration file builds successfully a fully functional ServiceConnection object. - * + * * * Over the file manager: * - If no TAPFileManager is provided, an exception must be thrown. * - If a class name toward a valid TAPFileManager is provided, a functional DefaultServiceConnection must be successfully built. * - An incorrect file manager value in the configuration file must generate an exception. - * + * * * Over the output format: * - If a SV format is badly expressed (test with "sv" and "sv()"), an exception must be thrown. * - If an unknown output format is provided an exception must be thrown. - * + * * Note: the good configuration of the TAPFactory built by the DefaultServiceConnection is tested in {@link TestConfigurableTAPFactory}. - * + * * @see ConfigurableServiceConnection#DefaultServiceConnection(Properties) */ @Test @@ -1040,6 +1051,33 @@ public class TestConfigurableServiceConnection { FunctionDef def = connection.getUDFs().iterator().next(); assertEquals("toto(a VARCHAR) -> VARCHAR", def.toString()); assertEquals(UDFToto.class, def.getUDFClass()); + assertNull(def.description); + }catch(Exception e){ + fail("This MUST have succeeded because the given list of UDFs contains valid items! \nCaught exception: " + getPertinentMessage(e)); + } + + // Valid list of UDFs containing one UDF with a class name AND a description: + try{ + ServiceConnection connection = new ConfigurableServiceConnection(udfsWithClassNameAndDescriptionProp); + assertNotNull(connection.getUDFs()); + assertEquals(1, connection.getUDFs().size()); + FunctionDef def = connection.getUDFs().iterator().next(); + assertEquals("toto(a VARCHAR) -> VARCHAR", def.toString()); + assertEquals(UDFToto.class, def.getUDFClass()); + assertEquals("Bla \"bla\".", def.description); + }catch(Exception e){ + fail("This MUST have succeeded because the given list of UDFs contains valid items! \nCaught exception: " + getPertinentMessage(e)); + } + + // Valid list of UDFs containing one UDF with empty optional parameters: + try{ + ServiceConnection connection = new ConfigurableServiceConnection(udfsWithEmptyOptParamsProp); + assertNotNull(connection.getUDFs()); + assertEquals(1, connection.getUDFs().size()); + FunctionDef def = connection.getUDFs().iterator().next(); + assertEquals("toto(a VARCHAR) -> VARCHAR", def.toString()); + assertNull(def.getUDFClass()); + assertNull(def.description); }catch(Exception e){ fail("This MUST have succeeded because the given list of UDFs contains valid items! \nCaught exception: " + getPertinentMessage(e)); } @@ -1050,7 +1088,7 @@ public class TestConfigurableServiceConnection { fail("This MUST have failed because the given UDFs list contains at least 2 items, whose one is ANY!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); - assertEquals("Wrong UDF declaration syntax: unexpected character at position 27 in the property " + KEY_UDFS + ": \"A\"! A UDF declaration must have one of the following syntaxes: \"[signature]\" or \"[signature,{className}]\".", e.getMessage()); + assertEquals("Wrong UDF declaration syntax: \"ANY\"! (position in the property " + KEY_UDFS + ": 27-30)", e.getMessage()); } // UDF with no brackets: @@ -1059,7 +1097,16 @@ public class TestConfigurableServiceConnection { fail("This MUST have failed because one UDFs list item has no brackets!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); - assertEquals("Wrong UDF declaration syntax: unexpected character at position 1 in the property " + KEY_UDFS + ": \"t\"! A UDF declaration must have one of the following syntaxes: \"[signature]\" or \"[signature,{className}]\".", e.getMessage()); + assertEquals("Wrong UDF declaration syntax: \"toto(a string)->VARCHAR\"! (position in the property " + KEY_UDFS + ": 1-24)", e.getMessage()); + } + + // UDF with a badly formatted description: + try{ + new ConfigurableServiceConnection(udfsWithWrongDescriptionFormatProp); + fail("This MUST have failed because one UDFs list item has too many parameters!"); + }catch(Exception e){ + assertEquals(TAPException.class, e.getClass()); + assertEquals("Wrong UDF declaration syntax: \"[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}, Blabla]\"! (position in the property " + KEY_UDFS + ": 1-67)", e.getMessage()); } // UDFs whose one item have more parts than supported: @@ -1068,7 +1115,7 @@ public class TestConfigurableServiceConnection { fail("This MUST have failed because one UDFs list item has too many parameters!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); - assertEquals("Wrong UDF declaration syntax: only two items (signature and class name) can be given within brackets. (position in the property " + KEY_UDFS + ": 58)", e.getMessage()); + assertEquals("Wrong UDF declaration syntax: \"[toto(a string)->VARCHAR, {adql.db.TestDBChecker$UDFToto}, \"Blabla\", foo]\"! (position in the property " + KEY_UDFS + ": 1-74)", e.getMessage()); } // UDF with missing definition part (or wrong since there is no comma): @@ -1113,7 +1160,7 @@ public class TestConfigurableServiceConnection { fail("This MUST have failed because one UDFs list item has no closing bracket!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); - assertEquals("Wrong UDF declaration syntax: missing closing bracket at position 24!", e.getMessage()); + assertEquals("Wrong UDF declaration syntax: \"[toto(a string)->VARCHAR\"! (position in the property " + KEY_UDFS + ": 1-25)", e.getMessage()); } // Valid custom TAPFactory: @@ -1198,7 +1245,7 @@ public class TestConfigurableServiceConnection { /** * A UWSFileManager to test the load of a UWSFileManager from the configuration file with a class path. - * + * * @author Grégory Mantelet (ARI) * @version 01/2015 * @see TestConfigurableServiceConnection#testDefaultServiceConnectionProperties() @@ -1212,7 +1259,7 @@ public class TestConfigurableServiceConnection { /** * A UserIdentifier which always return the same user...that's to say, all users are in a way still anonymous :-) * This class is only for test purpose. - * + * * @author Grégory Mantelet (ARI) * @version 02/2015 */ @@ -1235,7 +1282,7 @@ public class TestConfigurableServiceConnection { /** * TAPFactory just to test whether the property tap_factory works well. - * + * * @author Grégory Mantelet (ARI) * @version 03/2017 */ @@ -1268,7 +1315,7 @@ public class TestConfigurableServiceConnection { /** * ConfigurableTAPFactory just to test whether the property tap_factory allows TAPFactory * with a constructor (ServiceConnection, Properties). - * + * * @author Grégory Mantelet (ARI) * @version 03/2017 */ @@ -1300,7 +1347,7 @@ public class TestConfigurableServiceConnection { /** * TAPFactory just to test whether the property tap_factory is rejected when no constructor with a single parameter of type ServiceConnection exists. - * + * * @author Grégory Mantelet (ARI) * @version 02/2015 */ @@ -1326,7 +1373,7 @@ public class TestConfigurableServiceConnection { /** * TAPMetadata extension just to test whether it is possible to customize the output class of ConfigurableServiceConnection with the * metadata fetching methods "db" and "xml". - * + * * @author Grégory Mantelet (ARI) * @version 08/2015 */ @@ -1341,9 +1388,9 @@ public class TestConfigurableServiceConnection { /** * TAPMetadata extension just to test whether it is possible to customize the output class of ConfigurableServiceConnection with the * metadata fetching methods "db" and "xml". - * + * * <strong>This extension is however bad because it does not have any of the required constructor.</strong> - * + * * @author Grégory Mantelet (ARI) * @version 08/2015 */ @@ -1355,12 +1402,12 @@ public class TestConfigurableServiceConnection { /** * Custom TAPLog implementation. - * + * * <p><i> * Actually, for quick implementation, this class just extends * {@link DefaultTAPLog} (and so, implements TAPLog). * </i></p> - * + * * @author Grégory Mantelet (ARI) * @version 09/2017 */