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,)'