diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8e2e2905..e2ad5b2d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -25,6 +25,9 @@ jobs: - name: build and test cadc-sia run: cd cadc-sia && ../gradlew --info clean build javadoc checkstyleMain install + + - name: build and test cadc-conesearch + run: cd cadc-conesearch && ../gradlew --info clean build javadoc checkstyleMain install - name: build and test cadc-datalink run: cd cadc-datalink && ../gradlew --info clean build javadoc checkstyleMain install diff --git a/cadc-conesearch/README.md b/cadc-conesearch/README.md new file mode 100644 index 00000000..8c192385 --- /dev/null +++ b/cadc-conesearch/README.md @@ -0,0 +1,37 @@ +# cadc-conesearch + +Library containing logic to help implementors support the [Simple Cone Search Working Draft (1.1)](https://www.ivoa.net/documents/ConeSearch/20200828/WD-ConeSearch-1.1-20200828.html). + +## Building it + +Gradle 6 or higher is required. The DAL repository comes with a Gradle Wrapper. + +```shell +/workdir/dal $ ./gradlew -i -b cadc-conesearch/build.gradle clean build test +``` + +## Installing it + +Gradle 6 or higher is required. The DAL repository comes with a Gradle Wrapper. + +```shell +/workdir/dal $ ./gradlew -i -b cadc-conesearch/build.gradle clean build test install +``` + +## Implementing it + +Your `build.gradle` file should include it as a dependency from Maven Central: + +`build.gradle`: +```groovy +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + implementation 'org.opencadc:cadc-conesearch:1.0.0' +} +``` + +Implementors will want to use the `TAPQueryGenerator` class to create parameters that can be sent to a TAP service. diff --git a/cadc-conesearch/build.gradle b/cadc-conesearch/build.gradle new file mode 100644 index 00000000..638e0e27 --- /dev/null +++ b/cadc-conesearch/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'java-library' + id 'maven' + id 'maven-publish' + id 'checkstyle' +} + +repositories { + mavenCentral() + mavenLocal() +} + +sourceCompatibility = 1.8 +group = 'org.opencadc' +version = '1.0.0' + +description = 'OpenCADC Simple Cone Search Java API' +def git_url = 'https://github.com/opencadc/dal.git' + +dependencies { + implementation 'org.opencadc:cadc-dali:[1.2.17,2.0)' + implementation 'org.opencadc:cadc-rest:[1.3.12,2.0)' + implementation 'org.opencadc:cadc-util:[1.6.4,2.0)' + implementation 'org.opencadc:cadc-vosi:[1.4.4,2.0)' + + // Use JUnit test framework. + testImplementation 'junit:junit:4.13' +} + +apply from: '../opencadc.gradle' diff --git a/cadc-conesearch/src/main/java/org/opencadc/conesearch/ConeParameterValidator.java b/cadc-conesearch/src/main/java/org/opencadc/conesearch/ConeParameterValidator.java new file mode 100644 index 00000000..cc0f7135 --- /dev/null +++ b/cadc-conesearch/src/main/java/org/opencadc/conesearch/ConeParameterValidator.java @@ -0,0 +1,161 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2022. (c) 2022. + * Government of Canada Gouvernement du Canada + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits réservés + * + * NRC disclaims any warranties, Le CNRC dénie toute garantie + * expressed, implied, or énoncée, implicite ou légale, + * statutory, of any kind with de quelque nature que ce + * respect to the software, soit, concernant le logiciel, + * including without limitation y compris sans restriction + * any warranty of merchantability toute garantie de valeur + * or fitness for a particular marchande ou de pertinence + * purpose. NRC shall not be pour un usage particulier. + * liable in any event for any Le CNRC ne pourra en aucun cas + * damages, whether direct or être tenu responsable de tout + * indirect, special or general, dommage, direct ou indirect, + * consequential or incidental, particulier ou général, + * arising from the use of the accessoire ou fortuit, résultant + * software. Neither the name de l'utilisation du logiciel. Ni + * of the National Research le nom du Conseil National de + * Council of Canada nor the Recherches du Canada ni les noms + * names of its contributors may de ses participants ne peuvent + * be used to endorse or promote être utilisés pour approuver ou + * products derived from this promouvoir les produits dérivés + * software without specific prior de ce logiciel sans autorisation + * written permission. préalable et particulière + * par écrit. + * + * This file is part of the Ce fichier fait partie du projet + * OpenCADC project. OpenCADC. + * + * OpenCADC is free software: OpenCADC est un logiciel libre ; + * you can redistribute it and/or vous pouvez le redistribuer ou le + * modify it under the terms of modifier suivant les termes de + * the GNU Affero General Public la “GNU Affero General Public + * License as published by the License” telle que publiée + * Free Software Foundation, par la Free Software Foundation + * either version 3 of the : soit la version 3 de cette + * License, or (at your option) licence, soit (à votre gré) + * any later version. toute version ultérieure. + * + * OpenCADC is distributed in the OpenCADC est distribué + * hope that it will be useful, dans l’espoir qu’il vous + * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE + * without even the implied GARANTIE : sans même la garantie + * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ + * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF + * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence + * General Public License for Générale Publique GNU Affero + * more details. pour plus de détails. + * + * You should have received Vous devriez avoir reçu une + * a copy of the GNU Affero copie de la Licence Générale + * General Public License along Publique GNU Affero avec + * with OpenCADC. If not, see OpenCADC ; si ce n’est + * . pas le cas, consultez : + * . + * + * + ************************************************************************ + */ + +package org.opencadc.conesearch; + +import ca.nrc.cadc.dali.Circle; +import ca.nrc.cadc.dali.CommonParamValidator; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Validator for Cone Search parameters. + */ +public class ConeParameterValidator extends CommonParamValidator { + public static final String VERB_PARAM = "VERB"; + public static final String RA_PARAM = "RA"; + public static final String DEC_PARAM = "DEC"; + public static final String SR_PARAM = "SR"; + + public static final String MAXREC = "MAXREC"; + + static final int MIN_VERB_VALUE = 1; + static final int MID_VERB_VALUE = 2; + static final int MAX_VERB_VALUE = 3; + + public Circle validateCone(final Map> parameters) { + final String raValue = getFirstParameter(ConeParameterValidator.RA_PARAM, parameters); + final String decValue = getFirstParameter(ConeParameterValidator.DEC_PARAM, parameters); + final String searchRadiusValue = getFirstParameter(ConeParameterValidator.SR_PARAM, parameters); + final Map> circleValidateParams = new HashMap<>(); + circleValidateParams.put(CommonParamValidator.CIRCLE, Collections.singletonList( + String.format("%s %s %s", raValue, decValue, searchRadiusValue))); + + try { + final List validCircles = validateCircle(circleValidateParams); + + if (validCircles.isEmpty()) { + throw new IllegalArgumentException("No valid input cone position center provided."); + } else { + return validCircles.get(0); + } + } catch (NumberFormatException numberFormatException) { + throw new IllegalArgumentException("Cannot create cone position center from non-numeric input."); + } + } + + public int validateVERB(final Map> parameters) { + // If not VERB provided, default to 2. + if (parameters.get(VERB_PARAM) != null && !parameters.get(VERB_PARAM).isEmpty()) { + final List validIntegers = validateInteger(VERB_PARAM, parameters, Arrays.asList(MIN_VERB_VALUE, + MID_VERB_VALUE, + MAX_VERB_VALUE)); + if (validIntegers.isEmpty()) { + throw new IllegalArgumentException("VERB must be 1, 2, or 3."); + } else { + return validIntegers.get(0); + } + } else { + return MID_VERB_VALUE; + } + } + + int getMaxRec(final Map> parameters, final int defaultValue, final int maxValue) { + return validateInteger(ConeParameterValidator.MAXREC, parameters) + .stream() + .filter(i -> i <= maxValue) + .filter(i -> i > 0) + .findFirst() + .orElse(defaultValue); + } + + private List validateInteger(final String verbParam, final Map> parameters, + final List validValues) { + return super.validateInteger(verbParam, parameters) + .stream() + .filter(validValues::contains) + .collect(Collectors.toList()); + } + + private String getFirstParameter(final String key, final Map> requestParameters) { + final List values = requestParameters.get(key); + return (values == null || values.isEmpty()) ? null : values.get(0); + } + + public String getResponseFormat(final Map> parameters, final String contentType) { + return validateString(ConeParameterValidator.RESPONSEFORMAT, parameters, null) + .stream() + .findFirst() + .orElse(contentType); + } +} diff --git a/cadc-conesearch/src/main/java/org/opencadc/conesearch/TAPQueryGenerator.java b/cadc-conesearch/src/main/java/org/opencadc/conesearch/TAPQueryGenerator.java new file mode 100644 index 00000000..8e156f49 --- /dev/null +++ b/cadc-conesearch/src/main/java/org/opencadc/conesearch/TAPQueryGenerator.java @@ -0,0 +1,220 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2022. (c) 2022. + * Government of Canada Gouvernement du Canada + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits réservés + * + * NRC disclaims any warranties, Le CNRC dénie toute garantie + * expressed, implied, or énoncée, implicite ou légale, + * statutory, of any kind with de quelque nature que ce + * respect to the software, soit, concernant le logiciel, + * including without limitation y compris sans restriction + * any warranty of merchantability toute garantie de valeur + * or fitness for a particular marchande ou de pertinence + * purpose. NRC shall not be pour un usage particulier. + * liable in any event for any Le CNRC ne pourra en aucun cas + * damages, whether direct or être tenu responsable de tout + * indirect, special or general, dommage, direct ou indirect, + * consequential or incidental, particulier ou général, + * arising from the use of the accessoire ou fortuit, résultant + * software. Neither the name de l'utilisation du logiciel. Ni + * of the National Research le nom du Conseil National de + * Council of Canada nor the Recherches du Canada ni les noms + * names of its contributors may de ses participants ne peuvent + * be used to endorse or promote être utilisés pour approuver ou + * products derived from this promouvoir les produits dérivés + * software without specific prior de ce logiciel sans autorisation + * written permission. préalable et particulière + * par écrit. + * + * This file is part of the Ce fichier fait partie du projet + * OpenCADC project. OpenCADC. + * + * OpenCADC is free software: OpenCADC est un logiciel libre ; + * you can redistribute it and/or vous pouvez le redistribuer ou le + * modify it under the terms of modifier suivant les termes de + * the GNU Affero General Public la “GNU Affero General Public + * License as published by the License” telle que publiée + * Free Software Foundation, par la Free Software Foundation + * either version 3 of the : soit la version 3 de cette + * License, or (at your option) licence, soit (à votre gré) + * any later version. toute version ultérieure. + * + * OpenCADC is distributed in the OpenCADC est distribué + * hope that it will be useful, dans l’espoir qu’il vous + * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE + * without even the implied GARANTIE : sans même la garantie + * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ + * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF + * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence + * General Public License for Générale Publique GNU Affero + * more details. pour plus de détails. + * + * You should have received Vous devriez avoir reçu une + * a copy of the GNU Affero copie de la Licence Générale + * General Public License along Publique GNU Affero avec + * with OpenCADC. If not, see OpenCADC ; si ce n’est + * . pas le cas, consultez : + * . + * + * + ************************************************************************ + */ + +package org.opencadc.conesearch; + +import ca.nrc.cadc.dali.Circle; +import ca.nrc.cadc.dali.tables.votable.VOTableWriter; +import ca.nrc.cadc.util.StringUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +/** + * Class to produce an ADQL query from a set of Parameters + */ +public class TAPQueryGenerator { + private static final int DEF_MAXREC = 1000; + private static final int MAX_MAXREC = Integer.MAX_VALUE; + private static final Logger LOGGER = LogManager.getLogger(TAPQueryGenerator.class); + + protected final String tableName; + protected final String positionColumnName; + protected final String lowVerbositySelectList; + protected final String midVerbositySelectList; + protected final String highVerbositySelectList; + protected final Map> parameters; + + + /** + * + * @param tableName The name of the Catalog Table to query. + * @param positionColumnName The name of the positional column to compare against the cone's position, + * preferably spatially indexed.@param positionColumnName + * @param lowVerbositySelectList The query select list for VERB=1 + * @param midVerbositySelectList The query select list for VERB=2 + * @param highVerbositySelectList The query select list for VERB=3 + */ + public TAPQueryGenerator(final String tableName, final String positionColumnName, + final String lowVerbositySelectList, final String midVerbositySelectList, + final String highVerbositySelectList, final Map> parameters) { + if (!StringUtil.hasText(tableName) || !StringUtil.hasText(positionColumnName) + || !StringUtil.hasText(lowVerbositySelectList) || !StringUtil.hasText(midVerbositySelectList) + || !StringUtil.hasText(highVerbositySelectList) || parameters == null || parameters.isEmpty()) { + throw new IllegalArgumentException("tableName, positionColumnName, lowVerbositySelectList, " + + "midVerbositySelectList, highVerbositySelectList, and parameters " + + "are all required.\n" + + "(" + tableName + ", " + positionColumnName + ", " + + lowVerbositySelectList + ", " + midVerbositySelectList + ", " + + highVerbositySelectList + ", " + parameters + ")"); + } + + this.tableName = tableName; + this.positionColumnName = positionColumnName; + this.lowVerbositySelectList = lowVerbositySelectList; + this.midVerbositySelectList = midVerbositySelectList; + this.highVerbositySelectList = highVerbositySelectList; + this.parameters = parameters; + } + + /** + * Map with supported Simple Cone Search 1.1 parameters. + * ... + * + * @return map of parameter names and values + */ + public Map getParameterMap() { + final Map queryParameterMap = new HashMap<>(); + final ConeParameterValidator coneParameterValidator = new ConeParameterValidator(); + + // Obtain and, if necessary, provide a default RESPONSEFORMAT. + queryParameterMap.put(ConeParameterValidator.RESPONSEFORMAT, + coneParameterValidator.getResponseFormat(parameters, VOTableWriter.CONTENT_TYPE)); + + // Obtain and validate the VERB (verbosity) output. + final int outputVerbosity = coneParameterValidator.validateVERB(parameters); + + // Obtain and validate the MAXREC value with a default if necessary. + final int maxRecordCount = coneParameterValidator.getMaxRec(parameters, TAPQueryGenerator.DEF_MAXREC, + TAPQueryGenerator.MAX_MAXREC); + queryParameterMap.put(ConeParameterValidator.MAXREC, maxRecordCount); + + queryParameterMap.put("LANG", "ADQL"); + final String query = getQuery(outputVerbosity, coneParameterValidator.validateCone(parameters)); + LOGGER.debug("Cone Search TAP query:\n" + query); + queryParameterMap.put("QUERY", query); + return queryParameterMap; + } + + private String getQuery(final int outputVerbosity, final Circle circle) { + return "SELECT " + + getSelectList(outputVerbosity) + + " FROM " + + this.tableName + + " WHERE 1 = CONTAINS(" + + this.positionColumnName + + ", " + + "CIRCLE('ICRS', " + + circle.getCenter().getLongitude() + + ", " + + circle.getCenter().getLatitude() + + ", " + + circle.getRadius() + + "))"; + } + + + private String getSelectList(final int outputVerbosity) { + switch (outputVerbosity) { + case ConeParameterValidator.MIN_VERB_VALUE: { + return getLowVerbositySelectList(); + } + + case ConeParameterValidator.MAX_VERB_VALUE: { + return getHighVerbositySelectList(); + } + + // Default output is MID (2) + default: { + return getMidVerbositySelectList(); + } + } + } + + /** + * Obtain a select list for the Low Verbosity (1). + * @return String select columns, or "*", never null. + * Example: "ra, dec, footprint" + */ + public String getLowVerbositySelectList() { + return this.lowVerbositySelectList; + } + + /** + * Obtain a select list for the Medium Verbosity (2), which is the default. + * @return String select columns, or "*", never null. + * Example: "obs_id, release_date, ra, dec, footprint" + */ + public String getMidVerbositySelectList() { + return this.midVerbositySelectList; + } + + /** + * Obtain a select list for the High Verbosity (3). + * @return String select columns, or "*", never null. + * Example: "*" + */ + public String getHighVerbositySelectList() { + return this.highVerbositySelectList; + } +} diff --git a/cadc-conesearch/src/test/java/org/opencadc/conesearch/ConeParameterValidatorTest.java b/cadc-conesearch/src/test/java/org/opencadc/conesearch/ConeParameterValidatorTest.java new file mode 100644 index 00000000..4b3a05aa --- /dev/null +++ b/cadc-conesearch/src/test/java/org/opencadc/conesearch/ConeParameterValidatorTest.java @@ -0,0 +1,104 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2022. (c) 2022. + * Government of Canada Gouvernement du Canada + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits réservés + * + * NRC disclaims any warranties, Le CNRC dénie toute garantie + * expressed, implied, or énoncée, implicite ou légale, + * statutory, of any kind with de quelque nature que ce + * respect to the software, soit, concernant le logiciel, + * including without limitation y compris sans restriction + * any warranty of merchantability toute garantie de valeur + * or fitness for a particular marchande ou de pertinence + * purpose. NRC shall not be pour un usage particulier. + * liable in any event for any Le CNRC ne pourra en aucun cas + * damages, whether direct or être tenu responsable de tout + * indirect, special or general, dommage, direct ou indirect, + * consequential or incidental, particulier ou général, + * arising from the use of the accessoire ou fortuit, résultant + * software. Neither the name de l'utilisation du logiciel. Ni + * of the National Research le nom du Conseil National de + * Council of Canada nor the Recherches du Canada ni les noms + * names of its contributors may de ses participants ne peuvent + * be used to endorse or promote être utilisés pour approuver ou + * products derived from this promouvoir les produits dérivés + * software without specific prior de ce logiciel sans autorisation + * written permission. préalable et particulière + * par écrit. + * + * This file is part of the Ce fichier fait partie du projet + * OpenCADC project. OpenCADC. + * + * OpenCADC is free software: OpenCADC est un logiciel libre ; + * you can redistribute it and/or vous pouvez le redistribuer ou le + * modify it under the terms of modifier suivant les termes de + * the GNU Affero General Public la “GNU Affero General Public + * License as published by the License” telle que publiée + * Free Software Foundation, par la Free Software Foundation + * either version 3 of the : soit la version 3 de cette + * License, or (at your option) licence, soit (à votre gré) + * any later version. toute version ultérieure. + * + * OpenCADC is distributed in the OpenCADC est distribué + * hope that it will be useful, dans l’espoir qu’il vous + * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE + * without even the implied GARANTIE : sans même la garantie + * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ + * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF + * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence + * General Public License for Générale Publique GNU Affero + * more details. pour plus de détails. + * + * You should have received Vous devriez avoir reçu une + * a copy of the GNU Affero copie de la Licence Générale + * General Public License along Publique GNU Affero avec + * with OpenCADC. If not, see OpenCADC ; si ce n’est + * . pas le cas, consultez : + * . + * + * + ************************************************************************ + */ + +package org.opencadc.conesearch; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Test cone parameter validation. The validateCone() is tested in TAPQueryGeneratorTest. + */ +public class ConeParameterValidatorTest { + @Test + public void testValidateVerb() { + final ConeParameterValidator testSubject = new ConeParameterValidator(); + final Map> parameters = new HashMap<>(); + + Assert.assertEquals("Should be set to 2", 2, testSubject.validateVERB(parameters)); + + parameters.clear(); + parameters.put("VERB", Collections.singletonList("1")); + Assert.assertEquals("Should be set to 1", 1, testSubject.validateVERB(parameters)); + + parameters.clear(); + parameters.put("VERB", Collections.singletonList("6")); + try { + testSubject.validateVERB(parameters); + Assert.fail("6 is not a valid VERB."); + } catch (IllegalArgumentException illegalArgumentException) { + // Good + } + } +} diff --git a/cadc-conesearch/src/test/java/org/opencadc/conesearch/TAPQueryGeneratorTest.java b/cadc-conesearch/src/test/java/org/opencadc/conesearch/TAPQueryGeneratorTest.java new file mode 100644 index 00000000..ca6f057f --- /dev/null +++ b/cadc-conesearch/src/test/java/org/opencadc/conesearch/TAPQueryGeneratorTest.java @@ -0,0 +1,199 @@ +/* + ************************************************************************ + ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* + ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** + * + * (c) 2022. (c) 2022. + * Government of Canada Gouvernement du Canada + * National Research Council Conseil national de recherches + * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 + * All rights reserved Tous droits réservés + * + * NRC disclaims any warranties, Le CNRC dénie toute garantie + * expressed, implied, or énoncée, implicite ou légale, + * statutory, of any kind with de quelque nature que ce + * respect to the software, soit, concernant le logiciel, + * including without limitation y compris sans restriction + * any warranty of merchantability toute garantie de valeur + * or fitness for a particular marchande ou de pertinence + * purpose. NRC shall not be pour un usage particulier. + * liable in any event for any Le CNRC ne pourra en aucun cas + * damages, whether direct or être tenu responsable de tout + * indirect, special or general, dommage, direct ou indirect, + * consequential or incidental, particulier ou général, + * arising from the use of the accessoire ou fortuit, résultant + * software. Neither the name de l'utilisation du logiciel. Ni + * of the National Research le nom du Conseil National de + * Council of Canada nor the Recherches du Canada ni les noms + * names of its contributors may de ses participants ne peuvent + * be used to endorse or promote être utilisés pour approuver ou + * products derived from this promouvoir les produits dérivés + * software without specific prior de ce logiciel sans autorisation + * written permission. préalable et particulière + * par écrit. + * + * This file is part of the Ce fichier fait partie du projet + * OpenCADC project. OpenCADC. + * + * OpenCADC is free software: OpenCADC est un logiciel libre ; + * you can redistribute it and/or vous pouvez le redistribuer ou le + * modify it under the terms of modifier suivant les termes de + * the GNU Affero General Public la “GNU Affero General Public + * License as published by the License” telle que publiée + * Free Software Foundation, par la Free Software Foundation + * either version 3 of the : soit la version 3 de cette + * License, or (at your option) licence, soit (à votre gré) + * any later version. toute version ultérieure. + * + * OpenCADC is distributed in the OpenCADC est distribué + * hope that it will be useful, dans l’espoir qu’il vous + * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE + * without even the implied GARANTIE : sans même la garantie + * warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ + * or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF + * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence + * General Public License for Générale Publique GNU Affero + * more details. pour plus de détails. + * + * You should have received Vous devriez avoir reçu une + * a copy of the GNU Affero copie de la Licence Générale + * General Public License along Publique GNU Affero avec + * with OpenCADC. If not, see OpenCADC ; si ce n’est + * . pas le cas, consultez : + * . + * + * + ************************************************************************ + */ + +package org.opencadc.conesearch; + +import ca.nrc.cadc.dali.tables.votable.VOTableWriter; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TAPQueryGeneratorTest { + @Test + public void testInvalidJobParameters() { + final Map> parameters = new HashMap<>(); + + try { + new TAPQueryGenerator("badcat", "my_point", "ra, dec", + "obs_id, release_date, ra, dec", "*", + parameters); + Assert.fail("Should throw IllegalArgumentException here."); + } catch (IllegalArgumentException illegalArgumentException) { + // Good + } + + parameters.put("RA", Collections.singletonList("bogus")); + parameters.put("DEC", Collections.singletonList("bogus")); + parameters.put("SR", Collections.singletonList("bogus")); + + try { + final TAPQueryGenerator testSubject2 = new TAPQueryGenerator("badcat", "my_point", "ra, dec", + "obs_id, release_date, ra, dec", "*", + parameters); + testSubject2.getParameterMap(); + Assert.fail("Should throw IllegalArgumentException here."); + } catch (IllegalArgumentException illegalArgumentException) { + // Good + } + + parameters.clear(); + parameters.put("RA", Collections.singletonList("12.3")); + parameters.put("DEC", Collections.singletonList("45.6")); + parameters.put("SR", Collections.singletonList("0.7")); + parameters.put("VERB", Collections.singletonList("9")); + + try { + final TAPQueryGenerator testSubject3 = new TAPQueryGenerator("badcat", "my_point", "ra, dec", + "obs_id, release_date, ra, dec", "*", + parameters); + testSubject3.getParameterMap(); + Assert.fail("Should throw IllegalArgumentException here for VERB."); + } catch (IllegalArgumentException illegalArgumentException) { + // Good + } + + parameters.clear(); + parameters.put("RA", Collections.singletonList("12.3")); + parameters.put("DEC", Collections.singletonList("45.6")); + parameters.put("SR", Collections.singletonList("0.7")); + parameters.put("VERB", Collections.singletonList("bogus")); + + try { + final TAPQueryGenerator testSubject4 = new TAPQueryGenerator("badcat", "my_point", "ra, dec", + "obs_id, release_date, ra, dec", "*", + parameters); + testSubject4.getParameterMap(); + Assert.fail("Should throw IllegalArgumentException here for VERB."); + } catch (IllegalArgumentException illegalArgumentException) { + // Good + } + + parameters.clear(); + parameters.put("RA", Collections.singletonList("12.3")); + parameters.put("DEC", Collections.singletonList("108.6")); + parameters.put("SR", Collections.singletonList("0.7")); + parameters.put("VERB", Collections.singletonList("1")); + + try { + final TAPQueryGenerator testSubject5 = new TAPQueryGenerator("badcat", "my_point", "ra, dec", + "obs_id, release_date, ra, dec", "*", + parameters); + testSubject5.getParameterMap(); + Assert.fail("Should throw IllegalArgumentException here for invalid circle."); + } catch (IllegalArgumentException illegalArgumentException) { + // Good (invalid circle) + } + } + + @Test + public void testValidParameters() { + final Map> parameters = new HashMap<>(); + parameters.put("RA", Collections.singletonList("12.3")); + parameters.put("DEC", Collections.singletonList("45.6")); + parameters.put("SR", Collections.singletonList("0.7")); + + final TAPQueryGenerator testSubject = new TAPQueryGenerator("goodcat", "point_col", "ra, dec", + "obs_id, release_date, ra, dec", "*", + parameters); + + final Map tapQueryParameters1 = testSubject.getParameterMap(); + Assert.assertEquals("Wrong response format.", VOTableWriter.CONTENT_TYPE, + tapQueryParameters1.get("RESPONSEFORMAT")); + Assert.assertEquals("Wrong language", "ADQL", tapQueryParameters1.get("LANG")); + Assert.assertEquals("Wrong query.", + "SELECT obs_id, release_date, ra, dec " + + "FROM goodcat WHERE 1 = CONTAINS(point_col, CIRCLE('ICRS', 12.3, 45.6, 0.7))", + tapQueryParameters1.get("QUERY")); + Assert.assertEquals("Wrong max records", 1000, tapQueryParameters1.get("MAXREC")); + + // **** Next **** + + parameters.clear(); + parameters.put("RA", Collections.singletonList("12.3")); + parameters.put("DEC", Collections.singletonList("45.6")); + parameters.put("SR", Collections.singletonList("0.7")); + parameters.put("RESPONSEFORMAT", Collections.singletonList("tsv")); + parameters.put("MAXREC", Collections.singletonList("8000")); + parameters.put("VERB", Collections.singletonList("3")); + + final TAPQueryGenerator testSubject2 = new TAPQueryGenerator("goodcat", "point_col_2", "ra, dec", + "obs_id, release_date, ra, dec", "*", + parameters); + final Map tapQueryParameters2 = testSubject2.getParameterMap(); + Assert.assertEquals("Wrong response format.", "tsv", tapQueryParameters2.get("RESPONSEFORMAT")); + Assert.assertEquals("Wrong langauge", "ADQL", tapQueryParameters2.get("LANG")); + Assert.assertEquals("Wrong query.", + "SELECT * FROM goodcat WHERE 1 = CONTAINS(point_col_2, CIRCLE('ICRS', 12.3, 45.6, 0.7))", + tapQueryParameters2.get("QUERY")); + Assert.assertEquals("Wrong max records", 8000, tapQueryParameters2.get("MAXREC")); + } +} diff --git a/cadc-dali/build.gradle b/cadc-dali/build.gradle index 220822cd..d7151a14 100644 --- a/cadc-dali/build.gradle +++ b/cadc-dali/build.gradle @@ -14,7 +14,7 @@ sourceCompatibility = 1.8 group = 'org.opencadc' -version = '1.2.17' +version = '1.2.18' description = 'OpenCADC DALI library' def git_url = 'https://github.com/opencadc/dal' diff --git a/cadc-dali/src/main/java/ca/nrc/cadc/dali/CommonParamValidator.java b/cadc-dali/src/main/java/ca/nrc/cadc/dali/CommonParamValidator.java index a194a06f..07423028 100644 --- a/cadc-dali/src/main/java/ca/nrc/cadc/dali/CommonParamValidator.java +++ b/cadc-dali/src/main/java/ca/nrc/cadc/dali/CommonParamValidator.java @@ -79,6 +79,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; + import org.apache.log4j.Logger; /** @@ -92,6 +94,8 @@ public class CommonParamValidator { // DALI params public static final String RUNID = "RUNID"; public static final String RESPONSEFORMAT = "RESPONSEFORMAT"; + + public static final String MAXREC = "MAXREC"; // common params public static final String POS = "POS"; @@ -137,6 +141,33 @@ public String getResponseFormat(Map> params) { return values.get(0); } + public String getResponseFormat(Map> params, String defaultResponseFormat) { + final String providedResponseFormat = getResponseFormat(params); + return providedResponseFormat == null ? defaultResponseFormat : providedResponseFormat; + } + + /** + * Obtain the requested MAXREC value, or an optional default if provided. This value will be capped at the + * provided maxValue. + * @param params The parameters to pull the value from. + * @param defaultValue The default value to provide if nothing provided. + * @param maxValue The maximum value allowed. + * @return Max records count allowed, or default, or max value, or null. + */ + public Integer getMaxRec(Map> params, Integer defaultValue, Integer maxValue) { + final List validMaxRecs = validateInteger(MAXREC, params); + if (validMaxRecs.isEmpty()) { + return defaultValue; + } else { + final Integer maxRecValue = validMaxRecs.get(0); + if (maxRecValue > maxValue) { + return maxValue; + } else { + return maxRecValue; + } + } + } + private String scalarToInterval(String s) { String[] ss = s.split(" "); if (ss.length == 1) { @@ -304,6 +335,19 @@ public List validateString(String paramName, Map> p return ret; } + /** + * Validate an integer parameter against allowed set of values. + * @param paramName The parameter to look up in the params. + * @param params The map of Request parameters. + * @param allowedValues The values that are permitted. + * @return List of Integer objects, or empty List. Never null. + */ + public List validateInteger(String paramName, Map> params, + Collection allowedValues) { + final List parsedValues = this.validateInteger(paramName, params); + return parsedValues.stream().filter(allowedValues::contains).collect(Collectors.toList()); + } + public List validateInteger(String paramName, Map> params) { List ret = new ArrayList(); if (params == null) { diff --git a/cadc-dali/src/test/java/ca/nrc/cadc/dali/CommonParamValidatorTest.java b/cadc-dali/src/test/java/ca/nrc/cadc/dali/CommonParamValidatorTest.java index 00f32670..20c4414c 100644 --- a/cadc-dali/src/test/java/ca/nrc/cadc/dali/CommonParamValidatorTest.java +++ b/cadc-dali/src/test/java/ca/nrc/cadc/dali/CommonParamValidatorTest.java @@ -73,6 +73,9 @@ import ca.nrc.cadc.util.Log4jInit; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -460,6 +463,56 @@ public void testValidateID() { } } + @Test + public void testValidateInteger() { + final Map> params = new HashMap<>(); + params.put("param1", Arrays.asList("2", "4")); + params.put("param2", Arrays.asList("5", "9")); + try { + final List validIntegers = + paramValidator.validateInteger("param1", params, Arrays.asList(1, 2, 3)); + Assert.assertArrayEquals("Wrong list of ints.", new Integer[] { 2 }, validIntegers.toArray()); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + throw unexpected; + } + + try { + final List validIntegers = + paramValidator.validateInteger("param2", params, Arrays.asList(1, 2, 3)); + Assert.assertArrayEquals("Should be empty array of ints.", new Integer[] { }, + validIntegers.toArray()); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + throw unexpected; + } + } + + @Test + public void testGetResponseFormat() { + final Map> params = new HashMap<>(); + try { + Assert.assertEquals("Wrong RESPONSEFORMAT.", "tsv", + paramValidator.getResponseFormat(params, "tsv")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + throw unexpected; + } + + try { + params.put("RESPONSEFORMAT", Collections.singletonList("votable")); + Assert.assertEquals("Wrong RESPONSEFORMAT.", "votable", + paramValidator.getResponseFormat(params, "tsv")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + throw unexpected; + } + } + public void doValidateString(Method testMethod, String[] testParams, String[] testValues) throws Exception { if (testValues == null) { diff --git a/cadc-pkg-server/build.gradle b/cadc-pkg-server/build.gradle index 6da263b7..61de2a27 100644 --- a/cadc-pkg-server/build.gradle +++ b/cadc-pkg-server/build.gradle @@ -20,7 +20,8 @@ description = 'OpenCADC CADC package server library' def git_url = 'https://github.com/opencadc/dal' dependencies { - implementation 'org.apache.commons:commons-compress:[1.12,)' + implementation 'commons-codec:commons-codec:[1.16.1,2.0)' + implementation 'org.apache.commons:commons-compress:[1.26.1,2.0)' implementation 'org.opencadc:cadc-util:[1.6,2.0)' implementation 'org.opencadc:cadc-log:[1.0,)'