diff --git a/dap/Dockerfile b/dap/Dockerfile new file mode 100644 index 00000000..63bdf86f --- /dev/null +++ b/dap/Dockerfile @@ -0,0 +1,3 @@ +FROM images.opencadc.org/library/cadc-tomcat:1 + +COPY build/libs/dap.war /usr/share/tomcat/webapps diff --git a/dap/README.md b/dap/README.md new file mode 100644 index 00000000..bd69ffa2 --- /dev/null +++ b/dap/README.md @@ -0,0 +1,87 @@ +# dap + +`dap` is a prototype [Data Access Protocol](https://github.com/ivoa-std/DAP) service +that should work with any TAP service that provides an ivoa.ObsCore table (or view). It +also supports a mode that makes it operate as a compliant +[Simple Image Access 2.0](https://www.ivoa.net/documents/SIA/) service. + +## deployment +The `dap` war file can be renamed at deployment time in order to support an +alternate service name, including introducing additional path elements using the +[war-rename.conf](https://github.com/opencadc/docker-base/tree/master/cadc-tomcat) +feature. + +This service instance is expected to have a PostgreSQL database backend to store UWS +job information. This requirement could be removed in future to support a more lightweight +deployment of what is essentially a facade on a TAP service. + +## configuration +The following configuration files must be available in the `/config` directory. + +### catalina.properties +This file contains java system properties to configure the tomcat server and some of the java +libraries used in the service. + +See cadc-tomcat +for system properties related to the deployment environment. + +See cadc-util +for common system properties. + +`dap` includes multiple IdentityManager implementations to support authenticated access: + - See cadc-access-control-identity for CADC access-control system support. + - See cadc-gms for OIDC token support. + + `dap` requires one connection pool to store jobs: +``` +# database connection pools +org.opencadc.dap.uws.maxActive={max connections for jobs pool} +org.opencadc.dap.uws.username={database username for jobs pool} +org.opencadc.dap.uws.password={database password for jobs pool} +org.opencadc.dap.uws.url=jdbc:postgresql://{server}/{database} +``` +The _uws_ pool manages (create, alter, drop) uws tables and manages the uws content +(creates and modifies jobs in the uws schema when jobs are created and executed by users. + +### cadc-registry.properties +See cadc-registry. + +### dap.properties +`dap` must be configured to use a single TAP service to execute queries. +``` +# TAP service +org.opencadc.dap.queryService = {resourceID or TAP base URL} + +# run in backwards compatible SIAv2 mode (optional) +org.opencadc.dap.sia2mode = true | false + +# use a specific ObsCore table in the query service (optional, default: ivoa.ObsCore) +org.opencadc.dap.table = {table name} +``` +The _queryService_ is resolved by a registry lookup and that service is used to query +for CAOM content. It is assumed that this service is deployed "locally" since there can +be many calls and low latency is very desireable. + +The _sia2mode_ can be set to make the service behave as an SIA-2.0 service: this causes +the generated query to restrict the ObsCore.dataproduct_type values to `cube` and `image`. + +The _table_ to query can be set to something other than the default (`ivoa.ObsCore`). + +`dap` will attempt to use the caller's identity to query, but the details of this depend +on the configured IdentityManager and local A&A service configuration. + +## building it +``` +gradle clean build +docker build -t dap -f Dockerfile . +``` + +## checking it +``` +docker run --rm -it dap:latest /bin/bash +``` + +## running it +``` +docker run --rm --user tomcat:tomcat --volume=/path/to/external/config:/config:ro --name dap dap:latest +``` diff --git a/dap/VERSION b/dap/VERSION new file mode 100644 index 00000000..ff0eb44a --- /dev/null +++ b/dap/VERSION @@ -0,0 +1,6 @@ +## deployable containers have a semantic and build tag +# semantic version tag: major.minor +# build version tag: timestamp +VER=0.1.0 +TAGS="${VER} ${VER}-$(date -u +"%Y%m%dT%H%M%S")" +unset VER diff --git a/dap/build.gradle b/dap/build.gradle new file mode 100644 index 00000000..e1f0b31c --- /dev/null +++ b/dap/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'maven' + id 'war' + id 'checkstyle' +} + +repositories { + mavenCentral() + mavenLocal() +} + +apply from: '../opencadc.gradle' + +sourceCompatibility = 11 + +group = 'ca.nrc.cadc' + +war { + // Include the swagger-ui so that /sia provides the Sia API documentation + from(System.getenv('RPS') + '/resources/') { + include 'swagger-ui/' + } + from('.') { + include 'VERSION' + } +} + +dependencies { + providedCompile 'javax.servlet:javax.servlet-api:[3.1.0,)' + + compile 'org.opencadc:cadc-util:[1.6.1,)' + compile 'org.opencadc:cadc-cdp:[1.2.3,)' + compile 'org.opencadc:cadc-uws-server:[1.2.4,)' + compile 'org.opencadc:cadc-tap:[1.0,2.0)' + compile 'org.opencadc:cadc-vosi:[1.4.3,2.0)' + + runtime 'org.opencadc:cadc-registry:[1.7.7,)' + runtime 'org.opencadc:cadc-log:[1.0,)' + runtime 'org.opencadc:cadc-gms:[1.0.7,2.0)' + runtime 'org.opencadc:cadc-access-control-identity:[1.1.0,)' + + testCompile 'junit:junit:[4.0,)' + + intTestCompile 'org.opencadc:cadc-test-vosi:[1.0.11,)' + intTestCompile 'org.opencadc:cadc-test-uws:[1.1,)' +} + +configurations { + runtime.exclude group: 'javax.servlet' + runtime.exclude group: 'net.sourceforge.jtds' + runtime.exclude group: 'org.postgresql' + runtime.exclude group: 'org.restlet.jee' +} + diff --git a/dap/src/intTest/java/org/opencadc/dap/Constants.java b/dap/src/intTest/java/org/opencadc/dap/Constants.java new file mode 100644 index 00000000..71a6ab13 --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/Constants.java @@ -0,0 +1,85 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2024. (c) 2024. +* 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.dap; + +import java.net.URI; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler + */ +public class Constants { + private static final Logger log = Logger.getLogger(Constants.class); + + public static URI DAP_RESOURCE_ID = URI.create("ivo://opencadc.org/dap"); + public static URI SIA_RESOURCE_ID = URI.create("ivo://opencadc.org/sia"); + + private Constants() { + } +} diff --git a/dap/src/intTest/java/org/opencadc/dap/DapQueryErrorTest.java b/dap/src/intTest/java/org/opencadc/dap/DapQueryErrorTest.java new file mode 100644 index 00000000..093a964b --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/DapQueryErrorTest.java @@ -0,0 +1,127 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.conformance.uws2.JobResultWrapper; +import ca.nrc.cadc.conformance.uws2.SyncUWSTest; +import ca.nrc.cadc.dali.tables.votable.VOTableDocument; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.util.FileUtil; +import ca.nrc.cadc.util.Log4jInit; +import java.io.File; +import java.net.URI; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; + +/** + * + * @author pdowler + */ +public class DapQueryErrorTest extends SyncUWSTest { + + private static final Logger log = Logger.getLogger(DapQueryErrorTest.class); + + static { + Log4jInit.setLevel("org.opencadc.dap", Level.INFO); + Log4jInit.setLevel("ca.nrc.cadc.conformance.uws2", Level.INFO); + } + + public DapQueryErrorTest() { + super(Constants.DAP_RESOURCE_ID, Standards.DAP_QUERY_21); + + File testFile = FileUtil.getFileFromResource("SyncTest-ERROR-BAND.properties", DapQueryErrorTest.class); + if (testFile.exists()) { + File testDir = testFile.getParentFile(); + super.setPropertiesDir(testDir, "SyncTest-ERROR"); + } + } + + @Override + protected void validateResponse(JobResultWrapper result) { + Assert.assertEquals(400, result.responseCode); + Assert.assertEquals("application/x-votable+xml", result.contentType); + + try { + Assert.assertNotNull(result.syncOutput); + //VOTableDocument vot = VOTableHandler.getVOTable(result.syncOutput); + // because cadc-util HttpTransfer reads the error body and stores it in the Exception + VOTableDocument vot = VOTableHandler.getVOTable(result.throwable); + log.info(result.name + ": found valid VOTable"); + + String queryStatus = VOTableHandler.getQueryStatus(vot); + Assert.assertNotNull("QUERY_STATUS", queryStatus); + Assert.assertEquals("ERROR", queryStatus); + } catch (Exception ex) { + log.error("unexpected exception", ex); + Assert.fail("unexpected exception: " + ex); + } + } +} diff --git a/dap/src/intTest/java/org/opencadc/dap/DapQueryGetTest.java b/dap/src/intTest/java/org/opencadc/dap/DapQueryGetTest.java new file mode 100644 index 00000000..6e533a17 --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/DapQueryGetTest.java @@ -0,0 +1,125 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.conformance.uws2.JobResultWrapper; +import ca.nrc.cadc.conformance.uws2.SyncUWSTest; +import ca.nrc.cadc.dali.tables.votable.VOTableDocument; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.util.FileUtil; +import ca.nrc.cadc.util.Log4jInit; +import java.io.File; +import java.net.URI; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; + +/** + * + * @author pdowler + */ +public class DapQueryGetTest extends SyncUWSTest { + + private static final Logger log = Logger.getLogger(DapQueryGetTest.class); + + static { + Log4jInit.setLevel("org.opencadc.dap", Level.INFO); + Log4jInit.setLevel("ca.nrc.cadc.conformance.uws2", Level.INFO); + } + + public DapQueryGetTest() { + super(Constants.DAP_RESOURCE_ID, Standards.DAP_QUERY_21); + + File testFile = FileUtil.getFileFromResource("SyncTest-OK-BAND.properties", DapQueryGetTest.class); + if (testFile.exists()) { + File testDir = testFile.getParentFile(); + super.setPropertiesDir(testDir, "SyncTest-OK"); + } + } + + @Override + protected void validateResponse(JobResultWrapper result) { + Assert.assertEquals(200, result.responseCode); + Assert.assertEquals("application/x-votable+xml", result.contentType); + + try { + Assert.assertNotNull(result.syncOutput); + VOTableDocument vot = VOTableHandler.getVOTable(result.syncOutput); + log.info(result.name + ": found valid VOTable"); + + String queryStatus = VOTableHandler.getQueryStatus(vot); + Assert.assertNotNull("QUERY_STATUS", queryStatus); + Assert.assertEquals("OK", queryStatus); + } catch (Exception ex) { + log.error("unexpected exception", ex); + Assert.fail("unexpected exception: " + ex); + } + } +} diff --git a/dap/src/intTest/java/org/opencadc/dap/SiaQuery2ErrorTest.java b/dap/src/intTest/java/org/opencadc/dap/SiaQuery2ErrorTest.java new file mode 100644 index 00000000..53b48a7d --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/SiaQuery2ErrorTest.java @@ -0,0 +1,127 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.conformance.uws2.JobResultWrapper; +import ca.nrc.cadc.conformance.uws2.SyncUWSTest; +import ca.nrc.cadc.dali.tables.votable.VOTableDocument; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.util.FileUtil; +import ca.nrc.cadc.util.Log4jInit; +import java.io.File; +import java.net.URI; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; + +/** + * + * @author pdowler + */ +public class SiaQuery2ErrorTest extends SyncUWSTest { + + private static final Logger log = Logger.getLogger(SiaQuery2ErrorTest.class); + + static { + Log4jInit.setLevel("org.opencadc.dap", Level.INFO); + Log4jInit.setLevel("ca.nrc.cadc.conformance.uws2", Level.INFO); + } + + public SiaQuery2ErrorTest() { + super(Constants.SIA_RESOURCE_ID, Standards.SIA_QUERY_20); + + File testFile = FileUtil.getFileFromResource("SyncTest-ERROR-BAND.properties", SiaQuery2ErrorTest.class); + if (testFile.exists()) { + File testDir = testFile.getParentFile(); + super.setPropertiesDir(testDir, "SyncTest-ERROR"); + } + } + + @Override + protected void validateResponse(JobResultWrapper result) { + Assert.assertEquals(400, result.responseCode); + Assert.assertEquals("application/x-votable+xml", result.contentType); + + try { + Assert.assertNotNull(result.syncOutput); + //VOTableDocument vot = VOTableHandler.getVOTable(result.syncOutput); + // because cadc-util HttpTransfer reads the error body and stores it in the Exception + VOTableDocument vot = VOTableHandler.getVOTable(result.throwable); + log.info(result.name + ": found valid VOTable"); + + String queryStatus = VOTableHandler.getQueryStatus(vot); + Assert.assertNotNull("QUERY_STATUS", queryStatus); + Assert.assertEquals("ERROR", queryStatus); + } catch (Exception ex) { + log.error("unexpected exception", ex); + Assert.fail("unexpected exception: " + ex); + } + } +} diff --git a/dap/src/intTest/java/org/opencadc/dap/SiaQuery2GetTest.java b/dap/src/intTest/java/org/opencadc/dap/SiaQuery2GetTest.java new file mode 100644 index 00000000..9ae75116 --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/SiaQuery2GetTest.java @@ -0,0 +1,125 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.conformance.uws2.JobResultWrapper; +import ca.nrc.cadc.conformance.uws2.SyncUWSTest; +import ca.nrc.cadc.dali.tables.votable.VOTableDocument; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.util.FileUtil; +import ca.nrc.cadc.util.Log4jInit; +import java.io.File; +import java.net.URI; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; + +/** + * + * @author pdowler + */ +public class SiaQuery2GetTest extends SyncUWSTest { + + private static final Logger log = Logger.getLogger(SiaQuery2GetTest.class); + + static { + Log4jInit.setLevel("org.opencadc.dap", Level.INFO); + Log4jInit.setLevel("ca.nrc.cadc.conformance.uws2", Level.INFO); + } + + public SiaQuery2GetTest() { + super(Constants.SIA_RESOURCE_ID, Standards.SIA_QUERY_20); + + File testFile = FileUtil.getFileFromResource("SyncTest-OK-BAND.properties", SiaQuery2GetTest.class); + if (testFile.exists()) { + File testDir = testFile.getParentFile(); + super.setPropertiesDir(testDir, "SyncTest-OK"); + } + } + + @Override + protected void validateResponse(JobResultWrapper result) { + Assert.assertEquals(200, result.responseCode); + Assert.assertEquals("application/x-votable+xml", result.contentType); + + try { + Assert.assertNotNull(result.syncOutput); + VOTableDocument vot = VOTableHandler.getVOTable(result.syncOutput); + log.info(result.name + ": found valid VOTable"); + + String queryStatus = VOTableHandler.getQueryStatus(vot); + Assert.assertNotNull("QUERY_STATUS", queryStatus); + Assert.assertEquals("OK", queryStatus); + } catch (Exception ex) { + log.error("unexpected exception", ex); + Assert.fail("unexpected exception: " + ex); + } + } +} diff --git a/dap/src/intTest/java/org/opencadc/dap/VOTableHandler.java b/dap/src/intTest/java/org/opencadc/dap/VOTableHandler.java new file mode 100644 index 00000000..b5ca230b --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/VOTableHandler.java @@ -0,0 +1,131 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.dali.tables.votable.VOTableDocument; +import ca.nrc.cadc.dali.tables.votable.VOTableInfo; +import ca.nrc.cadc.dali.tables.votable.VOTableReader; +import ca.nrc.cadc.dali.tables.votable.VOTableResource; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.net.URL; +import org.apache.log4j.Logger; +import org.junit.Assert; + +/** + * + * @author pdowler + */ +public abstract class VOTableHandler { + + private static final Logger log = Logger.getLogger(VOTableHandler.class); + + private VOTableHandler() { + } + + static VOTableDocument getVOTable(Reader in) + throws IOException { + VOTableReader vrdr = new VOTableReader(); + return vrdr.read(in); + } + + static VOTableDocument getVOTable(Throwable ex) + throws IOException { + Reader in = new StringReader(ex.getMessage()); + return getVOTable(in); + } + + static VOTableDocument getVOTable(URL url) + throws IOException { + Reader in = new InputStreamReader(url.openStream()); + return getVOTable(in); + } + + static VOTableDocument getVOTable(byte[] ba) + throws IOException { + Reader in = new InputStreamReader(new ByteArrayInputStream(ba)); + return getVOTable(in); + } + + static String getQueryStatus(VOTableDocument vot) { + VOTableResource vr = vot.getResourceByType("results"); + Assert.assertNotNull(vr); + log.debug("found resource: " + vr.getName() + " " + vr.getType()); + for (VOTableInfo vi : vr.getInfos()) { + if ("QUERY_STATUS".equals(vi.getName())) { + return vi.getValue(); + } + } + return null; + } +} diff --git a/dap/src/intTest/java/org/opencadc/dap/VosiAvailabilityTest.java b/dap/src/intTest/java/org/opencadc/dap/VosiAvailabilityTest.java new file mode 100644 index 00000000..50c48570 --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/VosiAvailabilityTest.java @@ -0,0 +1,78 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2024. (c) 2024. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.vosi.AvailabilityTest; + +public class VosiAvailabilityTest extends AvailabilityTest { + public VosiAvailabilityTest() { + super(Constants.DAP_RESOURCE_ID); + } +} diff --git a/dap/src/intTest/java/org/opencadc/dap/VosiCapabilitiesTest.java b/dap/src/intTest/java/org/opencadc/dap/VosiCapabilitiesTest.java new file mode 100644 index 00000000..ac23b9ec --- /dev/null +++ b/dap/src/intTest/java/org/opencadc/dap/VosiCapabilitiesTest.java @@ -0,0 +1,78 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2024. (c) 2024. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.vosi.CapabilitiesTest; + +public class VosiCapabilitiesTest extends CapabilitiesTest { + public VosiCapabilitiesTest() { + super(Constants.DAP_RESOURCE_ID); + } +} diff --git a/dap/src/intTest/resources/SyncTest-ERROR-BAND.properties b/dap/src/intTest/resources/SyncTest-ERROR-BAND.properties new file mode 100644 index 00000000..3d73d3a6 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-BAND.properties @@ -0,0 +1,2 @@ +BAND=invalid +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-ERROR-CALIB.properties b/dap/src/intTest/resources/SyncTest-ERROR-CALIB.properties new file mode 100644 index 00000000..da0cc219 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-CALIB.properties @@ -0,0 +1,2 @@ +CALIB=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-ERROR-EXPTIME.properties b/dap/src/intTest/resources/SyncTest-ERROR-EXPTIME.properties new file mode 100644 index 00000000..62080f26 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-EXPTIME.properties @@ -0,0 +1,2 @@ +EXPTIME=invalid +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-ERROR-FOV.properties b/dap/src/intTest/resources/SyncTest-ERROR-FOV.properties new file mode 100644 index 00000000..6c833bae --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-FOV.properties @@ -0,0 +1,2 @@ +FOV=invalid +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-ERROR-POL.properties b/dap/src/intTest/resources/SyncTest-ERROR-POL.properties new file mode 100644 index 00000000..635db9d0 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-POL.properties @@ -0,0 +1,2 @@ +POL=invalid +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-ERROR-POS.properties b/dap/src/intTest/resources/SyncTest-ERROR-POS.properties new file mode 100644 index 00000000..d1b417c3 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-POS.properties @@ -0,0 +1,2 @@ +POS=Something bad +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-ERROR-SPATRES.properties b/dap/src/intTest/resources/SyncTest-ERROR-SPATRES.properties new file mode 100644 index 00000000..52d900ce --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-SPATRES.properties @@ -0,0 +1,2 @@ +SPATRES=invalid +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-ERROR-SPECRP.properties b/dap/src/intTest/resources/SyncTest-ERROR-SPECRP.properties new file mode 100644 index 00000000..051ce0ef --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-SPECRP.properties @@ -0,0 +1,2 @@ +SPECRP=2000 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-ERROR-TIME.properties b/dap/src/intTest/resources/SyncTest-ERROR-TIME.properties new file mode 100644 index 00000000..e89970ce --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-TIME.properties @@ -0,0 +1,2 @@ +TIME=invalid +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-ERROR-TIMERES.properties b/dap/src/intTest/resources/SyncTest-ERROR-TIMERES.properties new file mode 100644 index 00000000..cff741d5 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-ERROR-TIMERES.properties @@ -0,0 +1,2 @@ +TIMERES=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-BAND-scalar.properties b/dap/src/intTest/resources/SyncTest-OK-BAND-scalar.properties new file mode 100644 index 00000000..4869f98e --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-BAND-scalar.properties @@ -0,0 +1,3 @@ +BAND=500e-9 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-BAND.properties b/dap/src/intTest/resources/SyncTest-OK-BAND.properties new file mode 100644 index 00000000..053a3179 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-BAND.properties @@ -0,0 +1,3 @@ +BAND=500e-9 550e-9 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-CALIB.properties b/dap/src/intTest/resources/SyncTest-OK-CALIB.properties new file mode 100644 index 00000000..a84d7bef --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-CALIB.properties @@ -0,0 +1,4 @@ +CALIB=2 +CALIB=3 +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-COLLECTION.properties b/dap/src/intTest/resources/SyncTest-OK-COLLECTION.properties new file mode 100644 index 00000000..d6505ab0 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-COLLECTION.properties @@ -0,0 +1,3 @@ +COLLECTION=CFHT +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-DPTYPE.properties b/dap/src/intTest/resources/SyncTest-OK-DPTYPE.properties new file mode 100644 index 00000000..3eb7f31e --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-DPTYPE.properties @@ -0,0 +1,3 @@ +DPTYPE=image +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-EXPTIME.properties b/dap/src/intTest/resources/SyncTest-OK-EXPTIME.properties new file mode 100644 index 00000000..0c5f0f01 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-EXPTIME.properties @@ -0,0 +1,3 @@ +EXPTIME=120.0 240.0 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-FACILITY.properties b/dap/src/intTest/resources/SyncTest-OK-FACILITY.properties new file mode 100644 index 00000000..85b158f2 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-FACILITY.properties @@ -0,0 +1,3 @@ +FALICITY=CFHT +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-FORMAT.properties b/dap/src/intTest/resources/SyncTest-OK-FORMAT.properties new file mode 100644 index 00000000..ffd13ed3 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-FORMAT.properties @@ -0,0 +1,3 @@ +FORMAT=application/fits +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-FOV.properties b/dap/src/intTest/resources/SyncTest-OK-FOV.properties new file mode 100644 index 00000000..9dfed6d7 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-FOV.properties @@ -0,0 +1,3 @@ +FOV=1.0 2.0 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-ID.properties b/dap/src/intTest/resources/SyncTest-OK-ID.properties new file mode 100644 index 00000000..4c3de2b4 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-ID.properties @@ -0,0 +1,3 @@ +ID=12345 +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-INSTRUMENT.properties b/dap/src/intTest/resources/SyncTest-OK-INSTRUMENT.properties new file mode 100644 index 00000000..020105eb --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-INSTRUMENT.properties @@ -0,0 +1,3 @@ +INSTRUMENT=MEGAPipe +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-POL.properties b/dap/src/intTest/resources/SyncTest-OK-POL.properties new file mode 100644 index 00000000..f49ee244 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-POL.properties @@ -0,0 +1,5 @@ +POL=I +POL=Q +POL=U +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-POS-Circle.properties b/dap/src/intTest/resources/SyncTest-OK-POS-Circle.properties new file mode 100644 index 00000000..a33af980 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-POS-Circle.properties @@ -0,0 +1,3 @@ +POS=Circle 12 34 1.0 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-POS-OpenRange.properties b/dap/src/intTest/resources/SyncTest-OK-POS-OpenRange.properties new file mode 100644 index 00000000..0527a208 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-POS-OpenRange.properties @@ -0,0 +1,3 @@ +POS=Range 10 10 -Inf +Inf +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-POS-Polygon.properties b/dap/src/intTest/resources/SyncTest-OK-POS-Polygon.properties new file mode 100644 index 00000000..e8d4f0a2 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-POS-Polygon.properties @@ -0,0 +1,3 @@ +POS=Polygon 10 10 12 10 12 12 10 12 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-POS-Range.properties b/dap/src/intTest/resources/SyncTest-OK-POS-Range.properties new file mode 100644 index 00000000..4fa77cb3 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-POS-Range.properties @@ -0,0 +1,3 @@ +POS=Range 10 10 11 11 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-SPATRES.properties b/dap/src/intTest/resources/SyncTest-OK-SPATRES.properties new file mode 100644 index 00000000..757ad926 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-SPATRES.properties @@ -0,0 +1,3 @@ +SPATRES=0.05 0.10 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-SPECRP.properties b/dap/src/intTest/resources/SyncTest-OK-SPECRP.properties new file mode 100644 index 00000000..e6fa30e4 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-SPECRP.properties @@ -0,0 +1,3 @@ +SPECRP=1000 +Inf +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-TARGET.properties b/dap/src/intTest/resources/SyncTest-OK-TARGET.properties new file mode 100644 index 00000000..e300cd84 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-TARGET.properties @@ -0,0 +1,3 @@ +TARGET=m33 +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-TIME-scalar.properties b/dap/src/intTest/resources/SyncTest-OK-TIME-scalar.properties new file mode 100644 index 00000000..7a11f9d0 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-TIME-scalar.properties @@ -0,0 +1,3 @@ +TIME=54000.0 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-TIME.properties b/dap/src/intTest/resources/SyncTest-OK-TIME.properties new file mode 100644 index 00000000..e94f0f1c --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-TIME.properties @@ -0,0 +1,3 @@ +TIME=54000.0 54002.0 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/SyncTest-OK-TIMERES.properties b/dap/src/intTest/resources/SyncTest-OK-TIMERES.properties new file mode 100644 index 00000000..0c9e1922 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-TIMERES.properties @@ -0,0 +1,3 @@ +TIMERES=1.0 2.0 +MAXREC=1 +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/SyncTest-OK-multiple.properties b/dap/src/intTest/resources/SyncTest-OK-multiple.properties new file mode 100644 index 00000000..e89c0218 --- /dev/null +++ b/dap/src/intTest/resources/SyncTest-OK-multiple.properties @@ -0,0 +1,21 @@ +BAND=500e-9 550e-9 +CALIB=2 +CALIB=1 +COLLECTION=CFHT +DPTYPE=image +EXPTIME=120.0 240.0 +FACILITY=CFHT +FOV=1.0 2.0 +ID=12345 +INSTRUMENT=MEGAPipe +POL=I +POL=Q +POL=U +POS=Circle 12 34 1.0 +SPATRES=0.05 0.10 +SPECRP=1000 +Inf +TARGET=m33 +TIME=54000.0 54002.0 +TIMERES=1.0 2.0 +MAXREC=1 +expect:Content-Type=application/x-votable+xml diff --git a/dap/src/intTest/resources/VOTable-v1.3.xsd b/dap/src/intTest/resources/VOTable-v1.3.xsd new file mode 100644 index 00000000..1d4aaf3e --- /dev/null +++ b/dap/src/intTest/resources/VOTable-v1.3.xsd @@ -0,0 +1,568 @@ + + + + + VOTable is meant to serialize tabular documents in the + context of Virtual Observatory applications. This schema + corresponds to the VOTable document available from + http://www.ivoa.net/Documents/latest/VOT.html + + + + + + + + + + + + + + + + + + + + Accept UCD1+ + Accept also old UCD1 (but not / + %) including SIAP convention (with :) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + content-role was previsouly restricted as: + + + + + + + + + ]]>; is now a token. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deprecated in Version 1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deprecated in Version 1.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Added in Version 1.2: INFO for diagnostics + + + + + + + + + + + + + + + + + + + + + + + + + The 'encoding' attribute is added here to avoid + problems of code generators which do not properly + interpret the TR/TD structures. + 'encoding' was chosen because it appears in + appendix A.5 + + + + + + + + + The ID attribute is added here to the TR tag to avoid + problems of code generators which do not properly + interpret the TR/TD structures + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Added in Version 1.2: INFO for diagnostics + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Added in Version 1.2: INFO for diagnostics in several places + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dap/src/intTest/resources/disable-SyncTest-ERROR-COLLECTION.properties b/dap/src/intTest/resources/disable-SyncTest-ERROR-COLLECTION.properties new file mode 100644 index 00000000..8710d66a --- /dev/null +++ b/dap/src/intTest/resources/disable-SyncTest-ERROR-COLLECTION.properties @@ -0,0 +1,2 @@ +COLLECTION=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/disable-SyncTest-ERROR-DPTYPE.properties b/dap/src/intTest/resources/disable-SyncTest-ERROR-DPTYPE.properties new file mode 100644 index 00000000..cccebf2f --- /dev/null +++ b/dap/src/intTest/resources/disable-SyncTest-ERROR-DPTYPE.properties @@ -0,0 +1,2 @@ +DPTYPE=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/disable-SyncTest-ERROR-FACILITY.properties b/dap/src/intTest/resources/disable-SyncTest-ERROR-FACILITY.properties new file mode 100644 index 00000000..b83a012f --- /dev/null +++ b/dap/src/intTest/resources/disable-SyncTest-ERROR-FACILITY.properties @@ -0,0 +1,2 @@ +FACILITY=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/disable-SyncTest-ERROR-FORMAT.properties b/dap/src/intTest/resources/disable-SyncTest-ERROR-FORMAT.properties new file mode 100644 index 00000000..b18fd5cd --- /dev/null +++ b/dap/src/intTest/resources/disable-SyncTest-ERROR-FORMAT.properties @@ -0,0 +1,2 @@ +FORMAT=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/disable-SyncTest-ERROR-ID.properties b/dap/src/intTest/resources/disable-SyncTest-ERROR-ID.properties new file mode 100644 index 00000000..c2feaee9 --- /dev/null +++ b/dap/src/intTest/resources/disable-SyncTest-ERROR-ID.properties @@ -0,0 +1,2 @@ +ID=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/disable-SyncTest-ERROR-INSTRUMENT.properties b/dap/src/intTest/resources/disable-SyncTest-ERROR-INSTRUMENT.properties new file mode 100644 index 00000000..c041ae4b --- /dev/null +++ b/dap/src/intTest/resources/disable-SyncTest-ERROR-INSTRUMENT.properties @@ -0,0 +1,2 @@ +INSTRUMENT=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/intTest/resources/disable-SyncTest-ERROR-TARGET.properties b/dap/src/intTest/resources/disable-SyncTest-ERROR-TARGET.properties new file mode 100644 index 00000000..eb5b3840 --- /dev/null +++ b/dap/src/intTest/resources/disable-SyncTest-ERROR-TARGET.properties @@ -0,0 +1,2 @@ +TARGET=invalid +expect:Content-Type=application/x-votable+xml \ No newline at end of file diff --git a/dap/src/main/java/org/opencadc/dap/AdqlQueryGenerator.java b/dap/src/main/java/org/opencadc/dap/AdqlQueryGenerator.java new file mode 100644 index 00000000..a5376651 --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/AdqlQueryGenerator.java @@ -0,0 +1,367 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2019. (c) 2019. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.dali.Circle; +import ca.nrc.cadc.dali.Interval; +import ca.nrc.cadc.dali.Point; +import ca.nrc.cadc.dali.PolarizationState; +import ca.nrc.cadc.dali.Polygon; +import ca.nrc.cadc.dali.Range; +import ca.nrc.cadc.dali.Shape; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.log4j.Logger; + +/** + * Generate TAP query of the ivoa.ObsCore table from the SIAv2 query parameters. + * + * @author jburke + */ +public class AdqlQueryGenerator { + + private static Logger log = Logger.getLogger(AdqlQueryGenerator.class); + + private final boolean sia2mode; + private final String tableName; + private final Map> queryParams; + + /** + * The input query parameters as structured by the ParamExtractor in cadc-dali. + * + * @param query query input parameters + * @param tableName ivoa.ObsCore table name + * @see ca.nrc.cadc.dali.ParamExtractor + */ + public AdqlQueryGenerator(Map> query, String tableName, boolean sia2mode) { + this.tableName = tableName; + this.queryParams = query; + this.sia2mode = sia2mode; + } + + /** + * Map with the REQUEST, LANG, and QUERY parameters. + * + * @return map of parameter names and values + */ + public Map getParameterMap() { + Map map = new HashMap(); + map.put("LANG", "ADQL"); + String adql = getQuery(); + log.debug("SIAv2 query:\n" + adql); + map.put("QUERY", adql); + return map; + } + + protected String getQuery() { + StringBuilder query = new StringBuilder(); + query.append("SELECT * FROM "); + query.append(tableName); + + DapParamValidator dapParamValidator = new DapParamValidator(sia2mode); + final List pos = dapParamValidator.validatePOS(queryParams); + final List bands = dapParamValidator.validateBAND(queryParams); + final List times = dapParamValidator.validateTIME(queryParams); + final List pols = dapParamValidator.validatePOL(queryParams); + + if (sia2mode) { + query.append(" WHERE dataproduct_type IN ( 'image', 'cube' )"); + } else { + // subsequent append to query is much simpler if there is already a + // where clause and everything else is AND ... + query.append(" WHERE dataproduct_type IS NOT NULL"); + } + + if (!pos.isEmpty()) { + boolean needOr = false; + query.append(" AND "); + if (pos.size() > 1) { + query.append("("); + } + for (Shape s : pos) { + if (needOr) { + query.append(" OR "); + } + query.append("("); + + query.append("INTERSECTS("); + if (s instanceof Circle) { + Circle c = (Circle) s; + query.append("CIRCLE('ICRS',"); + query.append(c.getCenter().getLongitude()); + query.append(","); + query.append(c.getCenter().getLatitude()); + query.append(","); + query.append(c.getRadius()); + query.append(")"); + } else if (s instanceof Range) { + Range r = (Range) s; + query.append("RANGE_S2D("); + double ralb = 0.0; + double raub = 360.0; + double declb = -90.0; + double decub = 90.0; + if (r.getLongitude().getLower() != null) { + ralb = r.getLongitude().getLower(); + } + if (r.getLongitude().getUpper() != null) { + raub = r.getLongitude().getUpper(); + } + if (r.getLatitude().getLower() != null) { + declb = r.getLatitude().getLower(); + } + if (r.getLatitude().getUpper() != null) { + decub = r.getLatitude().getUpper(); + } + query.append(ralb); + query.append(","); + query.append(raub); + query.append(","); + query.append(declb); + query.append(","); + query.append(decub); + query.append(")"); + } else if (s instanceof Polygon) { + Polygon p = (Polygon) s; + query.append("POLYGON('ICRS',"); + boolean needComma = false; + for (Point v : p.getVertices()) { + if (needComma) { + query.append(","); + } + query.append(v.getLongitude()).append(",").append(v.getLatitude()); + needComma = true; + } + query.append(")"); + } + query.append(", s_region) = 1"); + query.append(")"); + needOr = true; + } + if (pos.size() > 1) { + query.append(")"); + } + } + + addNumericRangeConstraint(query, "em_min", "em_max", bands); + + addNumericRangeConstraint(query, "t_min", "t_max", times); + + if (!pols.isEmpty()) { + // for a single pattern-matching LIKE statement, we need to sort the POL values in canoncial order + // and stick in wildcard % whenever there is a gap + // use caom2 PolarizationState for now, possibly copy/move that to an OpenCADC module + //SortedSet polStates = new TreeSet(new PolarizationState.PolStateComparator()); + //for (String p : pols) + //{ + // polStates.add( PolarizationState.valueOf(p)); + //} + + query.append(" AND "); + if (pols.size() > 1) { + query.append(" ("); + } + boolean needOr = false; + for (PolarizationState p : pols) { + if (needOr) { + query.append(" OR "); + } + query.append("("); + query.append("pol_states LIKE '%").append(p.name()).append("%'"); + query.append(")"); + needOr = true; + } + if (pols.size() > 1) { + query.append(")"); + } + } + + List fovs = dapParamValidator.validateFOV(queryParams); + addNumericRangeConstraint(query, "s_fov", "s_fov", fovs); + + List ress = dapParamValidator.validateSPATRES(queryParams); + addNumericRangeConstraint(query, "s_resolution", "s_resolution", ress); + + List exptimes = dapParamValidator.validateEXPTIME(queryParams); + addNumericRangeConstraint(query, "t_exptime", "t_exptime", exptimes); + + List ids = dapParamValidator.validateID(queryParams); + addStringListConstraint(query, "obs_publisher_did", ids); + + List collections = dapParamValidator.validateCOLLECTION(queryParams); + addStringListConstraint(query, "obs_collection", collections); + + List facilities = dapParamValidator.validateFACILITY(queryParams); + addStringListConstraint(query, "facility_name", facilities); + + List instruments = dapParamValidator.validateINSTRUMENT(queryParams); + addStringListConstraint(query, "instrument_name", instruments); + + List calibs = dapParamValidator.validateCALIB(queryParams); + addIntegerListConstraint(query, "calib_level", calibs); + + List targets = dapParamValidator.validateTARGET(queryParams); + addStringListConstraint(query, "target_name", targets); + + List timeress = dapParamValidator.validateTIMERES(queryParams); + addNumericRangeConstraint(query, "t_resolution", "t_resolution", timeress); + + List specrps = dapParamValidator.validateSPECRP(queryParams); + addNumericRangeConstraint(query, "em_res_power", "em_res_power", specrps); + + List formats = dapParamValidator.validateFORMAT(queryParams); + addStringListConstraint(query, "access_format", formats); + + List dptypes = dapParamValidator.validateDPTYPE(queryParams); + addStringListConstraint(query, "dataproduct_type", dptypes); + + return query.toString(); + } + + private void addNumericRangeConstraint(StringBuilder query, String lbCol, String ubCol, List ranges) { + if (!ranges.isEmpty()) { + if (ranges.size() > 1) { + query.append(" AND ("); + } else { + query.append(" AND "); + } + boolean needOr = false; + for (Interval r : ranges) { + if (needOr) { + query.append(" OR "); + } + query.append("("); + if (lbCol.equals(ubCol) && !Double.isInfinite(r.getLower().doubleValue()) && !Double.isInfinite(r.getUpper().doubleValue())) { + // nicer syntax, better optimised in DB? + query.append(lbCol).append(" BETWEEN ").append(r.getLower()).append(" AND ").append(r.getUpper()); + } else { + if (!Double.isInfinite(r.getUpper().doubleValue())) { + query.append(lbCol).append(" <= ").append(r.getUpper()); + } + if (!Double.isInfinite(r.getLower().doubleValue()) && !Double.isInfinite(r.getUpper().doubleValue())) { + query.append(" AND "); + } + if (!Double.isInfinite(r.getLower().doubleValue())) { + query.append(r.getLower()).append(" <= ").append(ubCol); + } + } + query.append(")"); + needOr = true; + } + if (ranges.size() > 1) { + query.append(")"); + } + } + } + + private void addIntegerListConstraint(StringBuilder query, String column, List values) { + if (!values.isEmpty()) { + query.append(" AND ").append(column); + if (values.size() == 1) { + query.append(" = ").append(values.get(0)); + } else { + query.append(" IN ( "); + boolean first = true; + for (Integer value : values) { + if (first) { + first = false; + } else { + query.append(","); + } + query.append(value); + } + query.append(" )"); + } + } + } + + private void addStringListConstraint(StringBuilder query, String column, List values) { + if (!values.isEmpty()) { + query.append(" AND ").append(column); + if (values.size() == 1) { + query.append(" = '").append(values.get(0)).append("'"); + } else { + query.append(" IN ( "); + boolean first = true; + for (String value : values) { + if (first) { + first = false; + } else { + query.append(","); + } + query.append("'").append(value).append("'"); + } + query.append(" )"); + } + } + } + +} diff --git a/dap/src/main/java/org/opencadc/dap/DapConfig.java b/dap/src/main/java/org/opencadc/dap/DapConfig.java new file mode 100644 index 00000000..969fe1d6 --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/DapConfig.java @@ -0,0 +1,179 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2024. (c) 2024. +* 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.dap; + +import ca.nrc.cadc.auth.AuthMethod; +import ca.nrc.cadc.net.ResourceNotFoundException; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.util.InvalidConfigException; +import ca.nrc.cadc.util.MultiValuedProperties; +import ca.nrc.cadc.util.PropertiesReader; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler + */ +public class DapConfig { + + private static final Logger log = Logger.getLogger(DapConfig.class); + + private static final String CONFIG = "dap.properties"; + private static final String STD_OBSCORE_TABLE = "ivoa.ObsCore"; + + private static final String BASE_KEY = "org.opencadc.dap"; + private static final String QUERY_KEY = BASE_KEY + ".queryService"; + private static final String SIA2_KEY = BASE_KEY + ".sia2mode"; + private static final String TABLE_KEY = BASE_KEY + ".table"; + + private final URI queryService; + private final boolean sia2mode; + private final String tableName; + + public DapConfig() { + StringBuilder sb = new StringBuilder(); + try { + PropertiesReader r = new PropertiesReader(CONFIG); + MultiValuedProperties props = r.getAllProperties(); + + String qs = props.getFirstPropertyValue(QUERY_KEY); + URI qsURI = null; + sb.append("\n\t").append(QUERY_KEY).append(" - "); + if (qs == null) { + sb.append("MISSING"); + } else { + try { + qsURI = new URI(qs); + sb.append("OK"); + } catch (URISyntaxException ex) { + sb.append("ERROR invalid URI: " + qs); + } + } + + if (qsURI == null) { + throw new InvalidConfigException("invalid config: " + sb.toString()); + } + this.queryService = qsURI; + + String s2m = props.getFirstPropertyValue(SIA2_KEY); + this.sia2mode = "true".equals(s2m); + + String tn = props.getFirstPropertyValue(TABLE_KEY); + if (tn == null) { + this.tableName = STD_OBSCORE_TABLE; + } else { + this.tableName = tn; + } + } catch (InvalidConfigException ex) { + throw ex; + } + } + + public boolean isSia2mode() { + return sia2mode; + } + + public String getTableName() { + return tableName; + } + + public URI getQueryService() { + return queryService; + } + + public URL getTapSyncURL() throws MalformedURLException, ResourceNotFoundException { + if (queryService.getScheme().equals("ivo")) { + // registry lookup + RegistryClient reg = new RegistryClient(); + URL base = reg.getServiceURL(queryService, Standards.TAP_10, AuthMethod.ANON); + if (base == null) { + throw new ResourceNotFoundException("not found in registry: " + queryService); + } + return new URL(base.toExternalForm() + "/sync"); + } + + // assume direct URL + return new URL(queryService.toASCIIString() + "/sync"); + } + + public URL getAvailailityURL() throws MalformedURLException { + if (queryService.getScheme().equals("ivo")) { + // registry lookup + RegistryClient reg = new RegistryClient(); + return reg.getServiceURL(queryService, Standards.VOSI_AVAILABILITY, AuthMethod.ANON); + } + + // assume direct URL + return new URL(queryService.toASCIIString() + "/availability"); + } +} diff --git a/dap/src/main/java/org/opencadc/dap/DapInitAction.java b/dap/src/main/java/org/opencadc/dap/DapInitAction.java new file mode 100644 index 00000000..3b3c82b1 --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/DapInitAction.java @@ -0,0 +1,114 @@ +/* +************************************************************************ +******************* 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.dap; + +import ca.nrc.cadc.db.DBUtil; +import ca.nrc.cadc.rest.InitAction; +import ca.nrc.cadc.uws.server.impl.InitDatabaseUWS; +import java.net.URI; +import javax.sql.DataSource; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler + */ +public class DapInitAction extends InitAction { + private static final Logger log = Logger.getLogger(DapInitAction.class); + + private static final URI STD_DAP = URI.create("ivo://ivoa.net/std/DAP#query-2.1"); + private static final URI STD_SIA = URI.create("ivo://ivoa.net/std/SIA#query-2.0"); + private static final String CAP_PROPERTY = "org.opencadc.dap.standardID"; + + public DapInitAction() { + } + + @Override + public void doInit() { + // fix standardID in capabilities before CapInitAction loads + DapConfig dc = new DapConfig(); + if (dc.isSia2mode()) { + System.setProperty(CAP_PROPERTY, STD_SIA.toASCIIString()); + } else { + System.setProperty(CAP_PROPERTY, STD_DAP.toASCIIString()); + } + log.info("standardID: " + System.getProperty(CAP_PROPERTY)); + + try { + log.info("InitDatabaseUWS"); + DataSource uws = DBUtil.findJNDIDataSource("jdbc/uws"); + InitDatabaseUWS uwsi = new InitDatabaseUWS(uws, null, "uws"); + uwsi.doInit(); + log.info("InitDatabaseUWS: OK"); + } catch (Exception ex) { + throw new RuntimeException("INIT FAIL", ex); + } + } + + +} diff --git a/dap/src/main/java/org/opencadc/dap/DapParamValidator.java b/dap/src/main/java/org/opencadc/dap/DapParamValidator.java new file mode 100644 index 00000000..b3515e12 --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/DapParamValidator.java @@ -0,0 +1,176 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2019. (c) 2019. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.dali.CommonParamValidator; +import ca.nrc.cadc.dali.Interval; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler + */ +public class DapParamValidator extends CommonParamValidator { + + private static final Logger log = Logger.getLogger(DapParamValidator.class); + + // POS, BAND, TIME, POL, ID params inherited from dali.common.ParamValiator + + // SIA-2.0 params + public static final String FOV = "FOV"; + public static final String SPATRES = "SPATRES"; + public static final String EXPTIME = "EXPTIME"; + public static final String COLLECTION = "COLLECTION"; + public static final String FACILITY = "FACILITY"; + public static final String INSTRUMENT = "INSTRUMENT"; + public static final String DPTYPE = "DPTYPE"; + public static final String CALIB = "CALIB"; + public static final String TARGET = "TARGET"; + public static final String TIMERES = "TIMERES"; + public static final String SPECRP = "SPECRP"; + public static final String FORMAT = "FORMAT"; + + // used by the SiaRunner to pick out supported params only + static final List QUERY_PARAMS = Arrays.asList(POS, BAND, TIME, POL, ID, + FOV, SPATRES, EXPTIME, + COLLECTION, FACILITY, INSTRUMENT, DPTYPE, + CALIB, TARGET, TIMERES, SPECRP, FORMAT); + + // allowed data product types are image and cube + static final List SIA2_DPTYPES = Arrays.asList("cube", "image"); + + private final boolean sia2mode; + + public DapParamValidator(boolean sia2mode) { + this.sia2mode = sia2mode; + } + + private String scalar2interval(String s) { + String[] ss = s.split(" "); + if (ss.length == 1) { + return s + " " + s; + } + return s; + } + + public List validateFOV(Map> params) { + return validateNumericInterval(FOV, params); + } + + public List validateSPATRES(Map> params) { + return validateNumericInterval(SPATRES, params); + } + + public List validateEXPTIME(Map> params) { + return validateNumericInterval(EXPTIME, params); + } + + public List validateCOLLECTION(Map> params) { + return validateString(COLLECTION, params, null); + } + + public List validateFACILITY(Map> params) { + return validateString(FACILITY, params, null); + } + + public List validateINSTRUMENT(Map> params) { + return validateString(INSTRUMENT, params, null); + } + + public List validateDPTYPE(Map> params) { + if (sia2mode) { + return validateString(DPTYPE, params, SIA2_DPTYPES); + } + return validateString(DPTYPE, params, null); // allow all values + } + + public List validateCALIB(Map> params) { + return validateInteger(CALIB, params); + } + + public List validateTARGET(Map> params) { + return validateString(TARGET, params, null); + } + + public List validateTIMERES(Map> params) { + return validateNumericInterval(TIMERES, params); + } + + public List validateSPECRP(Map> params) { + return validateNumericInterval(SPECRP, params); + } + + public List validateFORMAT(Map> params) { + return validateString(FORMAT, params, null); + } +} diff --git a/dap/src/main/java/org/opencadc/dap/DapQueryRunner.java b/dap/src/main/java/org/opencadc/dap/DapQueryRunner.java new file mode 100644 index 00000000..627072ad --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/DapQueryRunner.java @@ -0,0 +1,227 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2024. (c) 2024. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.dali.MaxRecValidator; +import ca.nrc.cadc.dali.ParamExtractor; +import ca.nrc.cadc.dali.tables.votable.VOTableWriter; +import ca.nrc.cadc.net.HttpPost; +import ca.nrc.cadc.rest.SyncOutput; +import ca.nrc.cadc.uws.ErrorSummary; +import ca.nrc.cadc.uws.ErrorType; +import ca.nrc.cadc.uws.ExecutionPhase; +import ca.nrc.cadc.uws.Job; +import ca.nrc.cadc.uws.Result; +import ca.nrc.cadc.uws.server.JobRunner; +import ca.nrc.cadc.uws.server.JobUpdater; +import ca.nrc.cadc.uws.util.JobLogInfo; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.apache.log4j.Logger; + +/** + * Standard JobRunner implementation for DAP-2.x services. This implementation + * makes the following assumptions: + * + *
    + *
  • hard-coded to generate an ADQL query on the ivoa.ObsCore table + *
  • no support for authenticated calls, use of CDP, etc (TODO) + *
+ * + * @author pdowler + */ +public class DapQueryRunner implements JobRunner { + + private static Logger log = Logger.getLogger(DapQueryRunner.class); + + private static final Integer DEF_MAXREC = 1000; + private static final Integer MAX_MAXREC = null; + + private Job job; + private JobUpdater jobUpdater; + private SyncOutput syncOutput; + private JobLogInfo logInfo; + + public void setJob(Job job) { + this.job = job; + } + + public void setJobUpdater(JobUpdater ju) { + jobUpdater = ju; + } + + public void setSyncOutput(SyncOutput so) { + syncOutput = so; + } + + public void run() { + this.logInfo = new JobLogInfo(job); + + String startMessage = logInfo.start(); + log.info(startMessage); + + long t1 = System.currentTimeMillis(); + doit(); + long t2 = System.currentTimeMillis(); + + logInfo.setElapsedTime(t2 - t1); + + String endMessage = logInfo.end(); + log.info(endMessage); + } + + private void doit() { + URL url = null; + try { + ExecutionPhase ep = jobUpdater.setPhase(job.getID(), ExecutionPhase.QUEUED, ExecutionPhase.EXECUTING, new Date()); + if (!ExecutionPhase.EXECUTING.equals(ep)) { + String message = job.getID() + ": QUEUED -> EXECUTING [FAILED] -- DONE"; + logInfo.setSuccess(false); + logInfo.setMessage(message); + return; + } + log.debug(job.getID() + ": QUEUED -> EXECUTING [OK]"); + + MaxRecValidator mv = new MaxRecValidator(); + mv.setJob(job); + mv.setDefaultValue(DEF_MAXREC); + mv.setMaxValue(MAX_MAXREC); + Integer maxrec = mv.validate(); + + DapConfig conf = new DapConfig(); + ParamExtractor pe = new ParamExtractor(DapParamValidator.QUERY_PARAMS); + Map> queryParams = pe.getParameters(job.getParameterList()); + + // Get the ADQL request parameters. + AdqlQueryGenerator queryGenerator = new AdqlQueryGenerator(queryParams, conf.getTableName(), conf.isSia2mode()); + Map parameters = queryGenerator.getParameterMap(); + parameters.put("RESPONSEFORMAT", VOTableWriter.CONTENT_TYPE); + if (maxrec != null) { + parameters.put("MAXREC", maxrec); + } + + // the implementation assumes that the /tap/sync service follows the + // POST-redirect-GET (PrG) pattern; cadc-uws-server does + URL tapSyncURL = conf.getTapSyncURL(); + + // POST ADQL query to TAP but do not follow redirect to execute it. + HttpPost post = new HttpPost(tapSyncURL, parameters, false); + post.run(); + + // Create an ErrorSummary and throw RuntimeException if the POST failed. + if (post.getThrowable() != null) { + throw new RuntimeException("sync TAP query (" + tapSyncURL.toExternalForm() + + ") failed because " + + post.getThrowable().getMessage()); + } + + // redirect the caller to the G part of the /tap/sync PrG pattern + url = post.getRedirectURL(); + log.debug("redirectURL " + url); + syncOutput.setCode(303); + syncOutput.setHeader("Location", url.toExternalForm()); + + // Mark the Job as completed adding the URL to the query results. + List results = new ArrayList<>(); + results.add(new Result("result", new URI(url.toExternalForm()))); + jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING, ExecutionPhase.COMPLETED, results, new Date()); + } catch (Throwable t) { + logInfo.setSuccess(false); + logInfo.setMessage(t.getMessage()); + log.debug("FAIL", t); + + // temporary hack to convert IllegalArgumentException into UsageError: message + if (t instanceof IllegalArgumentException) { + t = new UsageError(t.getMessage()); + } + try { + VOTableWriter writer = new VOTableWriter(); + syncOutput.setHeader("Content-Type", VOTableWriter.CONTENT_TYPE); + // TODO: chose suitable response code here (assume bad input for now) + syncOutput.setCode(400); + writer.write(t, syncOutput.getOutputStream()); + } catch (IOException ioe) { + log.debug("Error writing error document " + ioe.getMessage()); + } + ErrorSummary errorSummary = new ErrorSummary(t.getMessage(), ErrorType.FATAL, url); + try { + jobUpdater.setPhase(job.getID(), ExecutionPhase.EXECUTING, ExecutionPhase.ERROR, + errorSummary, new Date()); + } catch (Throwable oops) { + log.debug("failed to set final error status after " + t, oops); + } + } + } +} diff --git a/dap/src/main/java/org/opencadc/dap/QueryJobManager.java b/dap/src/main/java/org/opencadc/dap/QueryJobManager.java new file mode 100644 index 00000000..a3fec9a1 --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/QueryJobManager.java @@ -0,0 +1,107 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.auth.AuthenticationUtil; +import ca.nrc.cadc.auth.IdentityManager; +import ca.nrc.cadc.uws.server.JobExecutor; +import ca.nrc.cadc.uws.server.JobPersistence; +import ca.nrc.cadc.uws.server.SimpleJobManager; +import ca.nrc.cadc.uws.server.SyncJobExecutor; +import ca.nrc.cadc.uws.server.impl.PostgresJobPersistence; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler + */ +public class QueryJobManager extends SimpleJobManager { + + private static final Logger log = Logger.getLogger(QueryJobManager.class); + + private static final Long MAX_EXEC_DURATION = 600L; + private static final Long MAX_DESTRUCTION = 7 * 24 * 3600L; // 1 week + private static final Long MAX_QUOTE = 600L; // same as exec since we don't queue + + public QueryJobManager() { + super(); + IdentityManager im = AuthenticationUtil.getIdentityManager(); + JobPersistence jobPersist = new PostgresJobPersistence(im); + + // exec jobs in request thread using custom SiaRunner + JobExecutor jobExec = new SyncJobExecutor(jobPersist, DapQueryRunner.class); + + super.setJobPersistence(jobPersist); + super.setJobExecutor(jobExec); + super.setMaxExecDuration(MAX_EXEC_DURATION); + super.setMaxDestruction(MAX_DESTRUCTION); + super.setMaxQuote(MAX_QUOTE); + } +} diff --git a/dap/src/main/java/org/opencadc/dap/ServiceAvailability.java b/dap/src/main/java/org/opencadc/dap/ServiceAvailability.java new file mode 100644 index 00000000..71652394 --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/ServiceAvailability.java @@ -0,0 +1,219 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2023. (c) 2023. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.auth.AuthMethod; +import ca.nrc.cadc.net.ResourceNotFoundException; +import ca.nrc.cadc.reg.Standards; +import ca.nrc.cadc.reg.client.LocalAuthority; +import ca.nrc.cadc.reg.client.RegistryClient; +import ca.nrc.cadc.vosi.Availability; +import ca.nrc.cadc.vosi.AvailabilityPlugin; +import ca.nrc.cadc.vosi.avail.CheckCertificate; +import ca.nrc.cadc.vosi.avail.CheckDataSource; +import ca.nrc.cadc.vosi.avail.CheckException; +import ca.nrc.cadc.vosi.avail.CheckResource; +import ca.nrc.cadc.vosi.avail.CheckWebService; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.NoSuchElementException; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler + */ +public class ServiceAvailability implements AvailabilityPlugin { + + private static final Logger log = Logger.getLogger(ServiceAvailability.class); + + private static String UWSDS_TEST = "select jobID from uws.Job limit 1"; + + public ServiceAvailability() { + } + + @Override + public void setAppName(String appName) { + //no op + } + + @Override + public boolean heartbeat() { + return true; + } + + public Availability getStatus() { + boolean isGood = true; + String note = "service is accepting queries"; + + try { + CheckResource cr = new CheckDataSource("jdbc/uws", UWSDS_TEST); + cr.check(); + + // TODO: this should be in a library somewhere + //cr = new CheckWcsLib(); + //cr.check(); + // certificate for A&A + File cert = new File(System.getProperty("user.home") + "/.ssl/cadcproxy.pem"); + if (cert.exists()) { + CheckCertificate checkCert = new CheckCertificate(cert); + checkCert.check(); + } + + // check other services we depend on + RegistryClient reg = new RegistryClient(); + URL url; + CheckResource checkResource; + + LocalAuthority localAuthority = new LocalAuthority(); + + try { + URI credURI = localAuthority.getServiceURI(Standards.CRED_PROXY_10.toASCIIString()); + if (credURI != null) { + url = reg.getServiceURL(credURI, Standards.VOSI_AVAILABILITY, AuthMethod.ANON); + if (url != null) { + checkResource = new CheckWebService(url); + checkResource.check(); + } else { + throw new ResourceNotFoundException("registry lookup - not found: " + credURI); + } + } else { + log.debug("not configured: " + Standards.CRED_PROXY_10.toASCIIString()); + } + } catch (NoSuchElementException ex) { // old LocalAuthority behaviour, subject to change + log.debug("not configured: " + Standards.CRED_PROXY_10.toASCIIString()); + } + + URI groupsURI = null; + try { + groupsURI = localAuthority.getServiceURI(Standards.GMS_SEARCH_10.toString()); + if (groupsURI != null) { + url = reg.getServiceURL(groupsURI, Standards.VOSI_AVAILABILITY, AuthMethod.ANON); + if (url != null) { + checkResource = new CheckWebService(url); + checkResource.check(); + } else { + log.warn("registry lookup - not found: " + groupsURI + " does not implement " + Standards.VOSI_AVAILABILITY); + } + } else { + log.debug("not configured: " + Standards.GMS_SEARCH_10.toASCIIString()); + } + } catch (NoSuchElementException ex) { // old LocalAuthority behaviour, subject to change + log.debug("not found: " + Standards.GMS_SEARCH_10.toASCIIString()); + } + + URI usersURI = null; + try { + usersURI = localAuthority.getServiceURI(Standards.UMS_USERS_01.toASCIIString()); + if (usersURI != null) { + if (groupsURI == null || !usersURI.equals(groupsURI)) { + url = reg.getServiceURL(usersURI, Standards.VOSI_AVAILABILITY, AuthMethod.ANON); + if (url != null) { + checkResource = new CheckWebService(url); + checkResource.check(); + } else { + throw new ResourceNotFoundException("registry lookup - not found: " + usersURI + + " " + Standards.VOSI_AVAILABILITY); + } + } else { + log.debug("skipped check because group and user servuices are both " + usersURI); + } + } + } catch (NoSuchElementException ex) { // old LocalAuthority behaviour, subject to change + log.debug("not found: " + Standards.UMS_USERS_01.toASCIIString()); + } + + DapConfig conf = new DapConfig(); + url = conf.getAvailailityURL(); + if (url != null) { + checkResource = new CheckWebService(url); + checkResource.check(); + } else { + throw new ResourceNotFoundException("registry lookup - not found: " + conf.getQueryService()); + } + } catch (CheckException ce) { + // tests determined that the resource is not working + isGood = false; + note = ce.getMessage(); + } catch (Throwable t) { + // the test itself failed + log.error("availability test failed", t); + isGood = false; + note = "test failed, reason: " + t; + } + return new Availability(isGood, note); + } + + public void setState(String string) { + //no-op + } +} diff --git a/dap/src/main/java/org/opencadc/dap/UsageError.java b/dap/src/main/java/org/opencadc/dap/UsageError.java new file mode 100644 index 00000000..d3dcd586 --- /dev/null +++ b/dap/src/main/java/org/opencadc/dap/UsageError.java @@ -0,0 +1,82 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2014. (c) 2014. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +/** + * + * @author pdowler + */ +public class UsageError extends Throwable { + + public UsageError(String message) { + super(message); + } + +} diff --git a/dap/src/main/webapp/META-INF/context.xml b/dap/src/main/webapp/META-INF/context.xml new file mode 100644 index 00000000..ce768e8d --- /dev/null +++ b/dap/src/main/webapp/META-INF/context.xml @@ -0,0 +1,18 @@ + + + + WEB-INF/web.xml + + + + diff --git a/dap/src/main/webapp/WEB-INF/web.xml b/dap/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..6f065247 --- /dev/null +++ b/dap/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,118 @@ + + + + + sia2 + + + index.html + + + + 1 + logControl + ca.nrc.cadc.log.LogControlServlet + + logLevel + info + + + logLevelPackages + + org.opencadc.dap + ca.nrc.cadc.dali + ca.nrc.cadc.uws + ca.nrc.cadc.reg + ca.nrc.cadc.rest + ca.nrc.cadc.vosi + ca.nrc.cadc.auth + org.opencadc.auth + + + + + + + 2 + QueryServlet + ca.nrc.cadc.uws.server.JobServlet + + init + org.opencadc.dap.DapInitAction + + + get + ca.nrc.cadc.uws.web.SyncGetAction + + + post + ca.nrc.cadc.uws.web.SyncPostAction + + + ca.nrc.cadc.uws.web.SyncPostAction.execOnPOST + true + + + ca.nrc.cadc.uws.server.JobManager + org.opencadc.dap.QueryJobManager + + + + + 3 + CapabilitiesServlet + ca.nrc.cadc.rest.RestServlet + + init + ca.nrc.cadc.vosi.CapInitAction + + + get + ca.nrc.cadc.vosi.CapGetAction + + + head + ca.nrc.cadc.vosi.CapHeadAction + + + input + /capabilities.xml + + + + + 4 + AvailabilityServlet + ca.nrc.cadc.vosi.AvailabilityServlet + + + + ca.nrc.cadc.vosi.AvailabilityPlugin + org.opencadc.dap.ServiceAvailability + + + + + QueryServlet + /query/* + + + + logControl + /logControl/* + + + + AvailabilityServlet + /availability + + + CapabilitiesServlet + /capabilities + + + diff --git a/dap/src/main/webapp/capabilities.xml b/dap/src/main/webapp/capabilities.xml new file mode 100644 index 00000000..83f0041f --- /dev/null +++ b/dap/src/main/webapp/capabilities.xml @@ -0,0 +1,38 @@ + + + + + + https://replace.me.com/dap/capabilities + + + + + + https://replace.me.com/dap/availability + + + + + + https://replace.me.com/dap/logControl + + + + + + + + + https://replace.me.com/dap/query + + + + + + + + diff --git a/dap/src/main/webapp/index.html b/dap/src/main/webapp/index.html new file mode 100644 index 00000000..88a1e77c --- /dev/null +++ b/dap/src/main/webapp/index.html @@ -0,0 +1,165 @@ + + + + + SIA-2.0 API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+
+ + diff --git a/dap/src/main/webapp/service.json b/dap/src/main/webapp/service.json new file mode 100644 index 00000000..cd6643d9 --- /dev/null +++ b/dap/src/main/webapp/service.json @@ -0,0 +1,369 @@ +{ + "swagger": "2.0", + "info": { + "version": "2", + "title": "SIA (Simple Image Access) web service", + "description": "IVOA SIA v2" + }, + "schemes": [ + "https" + ], + "basePath": "/sia", + "paths": { + "/query": { + "get": { + "description": "SIA-2.0 query of all collections. |\nThe SIA-2.0 implements all the query parameters described in the latest SIA-2.0 specification: POS, BAND, TIME, POL, FOV, SPATRES, EXPTIME, ID, COLLECTION, FACILITY, INSTRUMENT, DPTYPE, CALIB, TARGET, TIMERES, SPECRP, FORMAT.\n", + "tags": [ + "Simple Image Access 2.0" + ], + "produces": [ + "text/xml" + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "#/definitions/VOTable" + } + }, + "401": { + "description": "Unauthorized - User not authenticated" + }, + "404": { + "description": "Not Found - User not found" + }, + "500": { + "description": "Internal error" + }, + "503": { + "description": "Service busy" + }, + "default": { + "description": "Unexpeced error", + "schema": { + "$ref": "#/definitions/Error" + } + } + }, + "parameters": [ + { + "name": "POS", + "in": "query", + "description": "The POS parameter specifies the target coodinates (RA,DEC in degrees, ICRS) to search.", + "required": false, + "type": "string" + }, + { + "name": "MAXREC", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "BAND", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "TIME", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "POL", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "FOV", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "SPATRES", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "EXPTIME", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "ID", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "COLLECTION", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "FACILITY", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "INSTRUMENT", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "DPTYPE", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "CALIB", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "TARGET", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "TIMERES", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "SPECRP", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "FORMAT", + "in": "query", + "description": "", + "required": false, + "type": "string" + } + ] + }, + "post": { + "description": "SIA-2.0 query of all collections. |\nThe SIA-2.0 implements all the query parameters described in the latest SIA-2.0 specification: POS, BAND, TIME, POL, FOV, SPATRES, EXPTIME, ID, COLLECTION, FACILITY, INSTRUMENT, DPTYPE, CALIB, TARGET, TIMERES, SPECRP, FORMAT.\n", + "tags": [ + "Simple Image Access 2.0" + ], + "produces": [ + "text/xml" + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "#/definitions/VOTable" + } + }, + "401": { + "description": "Unauthorized - User not authenticated" + }, + "404": { + "description": "Not Found - User not found" + }, + "500": { + "description": "Internal error" + }, + "503": { + "description": "Service busy" + }, + "default": { + "description": "Unexpeced error", + "schema": { + "$ref": "#/definitions/Error" + } + } + }, + "parameters": [ + { + "name": "POS", + "in": "query", + "description": "The POS parameter specifies the target coodinates (RA,DEC in degrees, ICRS) to search.", + "required": false, + "type": "string" + }, + { + "name": "BAND", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "TIME", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "POL", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "FOV", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "SPATRES", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "EXPTIME", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "ID", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "COLLECTION", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "FACILITY", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "INSTRUMENT", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "DPTYPE", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "CALIB", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "TARGET", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "TIMERES", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "SPECRP", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "FORMAT", + "in": "query", + "description": "", + "required": false, + "type": "string" + }, + { + "name": "MAXREC", + "in": "query", + "description": "maximum number of records to return (default: 1000 maximum: no limit)", + "required": false, + "type": "string" + } + ] + } + }, + "/availability": { + "get": { + "tags": [ + "Support Interfaces" + ], + "summary": "VOSI Availability", + "description": "Indicates whether the service is operable and shows the reliability of the service for extended and scheduled requests. If the query parameter 'detail=min' is used, a light weight heart beat test will be performed. The heart beat test returns status 200 if the service is available.", + "parameters": [ + { + "name": "detail", + "in": "query", + "description": "specifies heart beat to be used to check for availability of this service, the value 'min' must be used, otherwise the full availability test will be performed", + "required": false, + "type": "string" + } + ] + } + }, + "/capabilities": { + "get": { + "summary": "VOSI Capabilities", + "tags": [ + "Support Interfaces" + ], + "description": "Provides the service metadata in the form of a list of Capability descriptions. Each of these descriptions is an \nXML element that:\n
    \n
  • states that the service provides a particular, IVOA-standard function;
  • \n
  • lists the interfaces for invoking that function;
  • \n
  • records any details of the implementation of the function that are not defined as default or constant in the standard for that function.
  • \n
\n" + } + } + } +} diff --git a/dap/src/test/java/org/opencadc/dap/AdqlQueryGeneratorTest.java b/dap/src/test/java/org/opencadc/dap/AdqlQueryGeneratorTest.java new file mode 100644 index 00000000..595193d0 --- /dev/null +++ b/dap/src/test/java/org/opencadc/dap/AdqlQueryGeneratorTest.java @@ -0,0 +1,307 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2011. (c) 2011. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import ca.nrc.cadc.util.CaseInsensitiveStringComparator; +import ca.nrc.cadc.util.Log4jInit; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author pdowler + */ +public class AdqlQueryGeneratorTest { + + private static final Logger log = Logger.getLogger(AdqlQueryGeneratorTest.class); + + static { + Log4jInit.setLevel("org.opencadc.dap", Level.INFO); + } + + boolean sia2mode = true; + + //@Test + public void testTemplate() { + + try { + + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testNoParams() { + + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore", sia2mode); + Map tapParams = gen.getParameterMap(); + + String lang = (String) tapParams.get("LANG"); + String adql = (String) tapParams.get("QUERY"); + + Assert.assertEquals("ADQL", lang); + + log.info("testNoParams ADQL:\n" + adql); + Assert.assertTrue("dataproduct_type", adql.contains("dataproduct_type")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testScalarInterval() { + + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + params.put("BAND", Arrays.asList("550e-9")); + params.put("TIME", Arrays.asList("54321.0")); + + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore", sia2mode); + Map tapParams = gen.getParameterMap(); + + String lang = (String) tapParams.get("LANG"); + String adql = (String) tapParams.get("QUERY"); + + Assert.assertEquals("ADQL", lang); + + log.info("testScalarInterval ADQL:\n" + adql); + Assert.assertTrue("dataproduct_type", adql.contains("dataproduct_type")); + Assert.assertTrue("em_min", adql.contains("em_min <=")); + Assert.assertTrue("em_max", adql.contains("<= em_max")); + Assert.assertTrue("t_min", adql.contains("t_min <=")); + Assert.assertTrue("t_max", adql.contains("<= t_max")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testSingleParams() { + + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + params.put("POS", Arrays.asList("CIRCLE 12.3 45.6 0.2")); + params.put("BAND", Arrays.asList("500e-9 700e-9")); + params.put("TIME", Arrays.asList("54321.0 55432.1")); + params.put("POL", Arrays.asList("I")); + params.put("FOV", Arrays.asList("0.5 +inf")); // > 0.5 deg + params.put("SPATRES", Arrays.asList("-inf 0.2")); // < 0.2 arcsec + params.put("EXPTIME", Arrays.asList("600.0 3600.0")); // 10-60 minutes + + params.put("ID", Arrays.asList("A12345")); + params.put("COLLECTION", Arrays.asList("CFHT")); + params.put("FACILITY", Arrays.asList("JCMT")); + params.put("INSTRUMENT", Arrays.asList("WIRCam")); + params.put("DPTYPE", Arrays.asList("cube")); + params.put("CALIB", Arrays.asList("2")); + params.put("TARGET", Arrays.asList("M33")); + params.put("TIMERES", Arrays.asList("1.0 2.0")); + params.put("SPECRP", Arrays.asList("-inf 500")); + params.put("FORMAT", Arrays.asList("application/fits")); + + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore", sia2mode); + String adql = gen.getQuery(); + log.info("testSingleParams ADQL:\n" + adql); + + Assert.assertTrue("dataproduct_type", adql.contains("dataproduct_type")); + + Assert.assertTrue("s_region", adql.contains("s_region")); + Assert.assertTrue("em_min", adql.contains("em_min")); + Assert.assertTrue("em_max", adql.contains("em_max")); + Assert.assertTrue("t_min", adql.contains("t_min")); + Assert.assertTrue("t_max", adql.contains("t_max")); + Assert.assertTrue("pol_states", adql.contains("pol_states")); + + Assert.assertTrue("s_fov", adql.contains("s_fov")); + Assert.assertTrue("s_resolution", adql.contains("s_resolution")); + Assert.assertTrue("t_exptime", adql.contains("t_exptime")); + + Assert.assertTrue("obs_publisher_did", adql.contains("obs_publisher_did")); + Assert.assertTrue("obs_collection", adql.contains("obs_collection")); + Assert.assertTrue("facility_name", adql.contains("facility_name")); + Assert.assertTrue("instrument_name", adql.contains("instrument_name")); + Assert.assertTrue("dataproduct_type", adql.contains("dataproduct_type")); + Assert.assertTrue("calib_level", adql.contains("calib_level")); + Assert.assertTrue("target_name", adql.contains("target_name")); + Assert.assertTrue("t_resolution", adql.contains("t_resolution")); + Assert.assertTrue("em_res_power", adql.contains("em_res_power")); + Assert.assertTrue("access_format", adql.contains("access_format")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testMultipleParams() { + + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + params.put("POS", Arrays.asList("CIRCLE 12.3 45.6 0.2", "RANGE +20 +22 -10 -8", "POLYGON 10 10 12 10 11 11")); + params.put("BAND", Arrays.asList("500e-9 700e-9", "200e-9 400e-9")); + params.put("TIME", Arrays.asList("54321.0 55432.1", "56789.0 +Inf")); + params.put("POL", Arrays.asList("I", "Q", "U")); + params.put("FOV", Arrays.asList("0.5 +Inf", "-Inf 2.0")); + params.put("SPATRES", Arrays.asList("-Inf 0.2", "0.02 +Inf")); + params.put("EXPTIME", Arrays.asList("10 20", "600.0 3600.0")); + + params.put("ID", Arrays.asList("A12345", "12345B")); + params.put("COLLECTION", Arrays.asList("CFHT", "JCMT")); + params.put("FACILITY", Arrays.asList("JCMT", "BLAST")); + params.put("INSTRUMENT", Arrays.asList("WIRCam", "MEGAPipe")); + params.put("DPTYPE", Arrays.asList("cube", "image")); + params.put("CALIB", Arrays.asList("2", "4")); + params.put("TARGET", Arrays.asList("M33", "LMC")); + params.put("TIMERES", Arrays.asList("1.0 2.0", "-Inf 3.0")); + params.put("SPECRP", Arrays.asList("-Inf 500", "200 300")); + params.put("FORMAT", Arrays.asList("application/fits", "text/xml")); + + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore", sia2mode); + String adql = gen.getQuery(); + log.info("testMultipleParams ADQL:\n" + adql); + + Assert.assertTrue("dataproduct_type", adql.contains("dataproduct_type")); + + Assert.assertTrue("s_region", adql.contains("s_region")); + Assert.assertTrue("em_min", adql.contains("em_min")); + Assert.assertTrue("em_max", adql.contains("em_max")); + Assert.assertTrue("t_min", adql.contains("t_min")); + Assert.assertTrue("t_max", adql.contains("t_max")); + Assert.assertTrue("pol_states", adql.contains("pol_states")); + + Assert.assertTrue("s_fov", adql.contains("s_fov")); + Assert.assertTrue("s_resolution", adql.contains("s_resolution")); + Assert.assertTrue("t_exptime", adql.contains("t_exptime")); + + Assert.assertTrue("obs_publisher_did", adql.contains("obs_publisher_did")); + Assert.assertTrue("obs_collection", adql.contains("obs_collection")); + Assert.assertTrue("facility_name", adql.contains("facility_name")); + Assert.assertTrue("instrument_name", adql.contains("instrument_name")); + Assert.assertTrue("dataproduct_type", adql.contains("dataproduct_type")); + Assert.assertTrue("calib_level", adql.contains("calib_level")); + Assert.assertTrue("target_name", adql.contains("target_name")); + Assert.assertTrue("t_resolution", adql.contains("t_resolution")); + Assert.assertTrue("em_res_power", adql.contains("em_res_power")); + Assert.assertTrue("access_format", adql.contains("access_format")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testCoordRanges() { + + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + params.put("POS", Arrays.asList("RANGE 0 360 -2 2", "RANGE 10 20 -90 90", "RANGE 1 2 3 4")); + + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore", sia2mode); + String adql = gen.getQuery(); + log.info("testCoordRanges ADQL:\n" + adql); + + Assert.assertTrue("dataproduct_type", adql.contains("dataproduct_type")); + Assert.assertTrue("s_region", adql.contains("s_region")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testAltTableNames() { + + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "schema.TableName", sia2mode); + String adql = gen.getQuery(); + log.info("testCoordRanges ADQL:\n" + adql); + + String selectStmt = "SELECT * FROM schema.TableName WHERE"; + Assert.assertTrue("schema.TableName", adql.contains(selectStmt)); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } +} diff --git a/dap/src/test/java/org/opencadc/dap/DapParamValidatorTest.java b/dap/src/test/java/org/opencadc/dap/DapParamValidatorTest.java new file mode 100644 index 00000000..c9525933 --- /dev/null +++ b/dap/src/test/java/org/opencadc/dap/DapParamValidatorTest.java @@ -0,0 +1,503 @@ +/* +************************************************************************ +******************* CANADIAN ASTRONOMY DATA CENTRE ******************* +************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** +* +* (c) 2019. (c) 2019. +* 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 : +* . +* +* $Revision: 5 $ +* +************************************************************************ + */ + +package org.opencadc.dap; + +import org.opencadc.dap.DapParamValidator; +import ca.nrc.cadc.dali.DoubleInterval; +import ca.nrc.cadc.util.CaseInsensitiveStringComparator; +import ca.nrc.cadc.util.Log4jInit; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author pdowler + */ +public class DapParamValidatorTest { + + private static final Logger log = Logger.getLogger(DapParamValidatorTest.class); + + static { + Log4jInit.setLevel("ca.nrc.cadc.sia2", Level.INFO); + } + + DapParamValidator dapParamValidator = new DapParamValidator(false); // default: DAP mode + + @Test + public void testValidateFOV() { + + String[] testParams = new String[]{"FOV", "fov", "FoV"}; + + try { + List empty = dapParamValidator.validateFOV(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateFOV", Map.class); + doValidateNumeric(dapParamValidator, m, "FOV", testParams); + + // invalid: code is more or less tested already in testValidateBand + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateSPATRES() { + + String[] testParams = new String[]{"SPATRES", "spatres", "SpAtReS"}; + + try { + List empty = dapParamValidator.validateSPATRES(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateSPATRES", Map.class); + doValidateNumeric(dapParamValidator, m, "SPATRES", testParams); + + // invalid: code is more or less tested already in testValidateBand + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateEXPTIME() { + + String[] testParams = new String[]{"EXPTIME", "exptime", "ExPtImE"}; + + try { + List empty = dapParamValidator.validateEXPTIME(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateEXPTIME", Map.class); + doValidateNumeric(dapParamValidator, m, "EXPTIME", testParams); + + // invalid: code is more or less tested already in testValidateBand + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateCOLLECTION() { + try { + String[] testParams = new String[]{"COLLECTION", "collection", "CoLlEcTiOn"}; + + // null arg check + List empty = dapParamValidator.validateCOLLECTION(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateCOLLECTION", Map.class); + doValidateString(dapParamValidator, m, testParams, null); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateFACILITY() { + try { + String[] testParams = new String[]{"FACILITY", "facility", "FaCiLiTy"}; + + // null arg check + List empty = dapParamValidator.validateFACILITY(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateFACILITY", Map.class); + doValidateString(dapParamValidator, m, testParams, null); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateINSTRUMENT() { + try { + String[] testParams = new String[]{"INSTRUMENT", "instrument", "InStRuMeNt"}; + + // null arg check + List empty = dapParamValidator.validateINSTRUMENT(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateINSTRUMENT", Map.class); + doValidateString(dapParamValidator, m, testParams, null); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateDPTYPE() { + // test invalid value: SIAv2 mode + DapParamValidator sia = new DapParamValidator(true); + + try { + String[] testParams = new String[]{"DPTYPE", "dptype", "DpTyPe"}; + String[] testValues = new String[]{"cube", "image"}; + + // null arg check + List empty = dapParamValidator.validateDPTYPE(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateDPTYPE", Map.class); + doValidateString(sia, m, testParams, testValues); + + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + List vals = new ArrayList(); + vals.add("FOO"); + params.clear(); + params.put(testParams[0], vals); + try { + List ret = sia.validateDPTYPE(params); + Assert.fail("expected IllegalArgumentException,. got: " + ret.size() + " String(s)"); + } catch (IllegalArgumentException expected) { + log.debug("caught expected: " + expected); + } + + // DAP validator: no invalid values + testValues = new String[] {"cube", "image", "spectrum", "timeseries", "something-else" }; + doValidateString(dapParamValidator, m, testParams, testValues); + + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateCALIB() { + try { + String[] testParams = new String[]{"CALIB", "calib", "CaLiB"}; + + List empty = dapParamValidator.validateCALIB(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateCALIB", Map.class); + doValidateInteger(dapParamValidator, m, "CALIB", testParams); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateTARGET() { + try { + String[] testParams = new String[]{"TARGET", "target", "TaRgEt"}; + + // null arg check + List empty = dapParamValidator.validateTARGET(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateTARGET", Map.class); + doValidateString(dapParamValidator, m, testParams, null); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateTIMERES() { + try { + String[] testParams = new String[]{"TIMERES", "timeres", "TiMeReS"}; + + List empty = dapParamValidator.validateTIMERES(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateTIMERES", Map.class); + doValidateNumeric(dapParamValidator, m, "TIMERES", testParams); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateSPECRP() { + try { + String[] testParams = new String[]{"SPECRP", "specrp", "SpEcRp"}; + + List empty = dapParamValidator.validateSPECRP(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateSPECRP", Map.class); + doValidateNumeric(dapParamValidator, m, "SPECRP", testParams); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateFORMAT() { + try { + String[] testParams = new String[]{"FORMAT", "format", "FoRmAt"}; + + // null arg check + List empty = dapParamValidator.validateFORMAT(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = DapParamValidator.class.getMethod("validateFORMAT", Map.class); + doValidateString(dapParamValidator, m, testParams, null); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateCustomParam() { + try { + List empty = dapParamValidator.validateString("FOO", null, null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + List vals = new ArrayList(); + vals.add("abc"); + params.put("FOO", vals); + List strs = dapParamValidator.validateString("FOO", params, null); + Assert.assertNotNull(strs); + Assert.assertEquals(1, strs.size()); + String s = strs.get(0); + Assert.assertEquals("abc", s); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + public void doValidateString(DapParamValidator instance, Method testMethod, String[] testParams, String[] testValues) + throws Exception { + if (testValues == null) { + testValues = new String[]{ + "12345", + "ABCDEF", + "abcdef", + "a1b2c3", + "A1b2C3" + }; + } + + int[] len = new int[]{1, testValues.length / 2, testValues.length}; + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + for (String tp : testParams) { + for (int i = 0; i < len.length; i++) { + List vals = new ArrayList(); + for (int j = 0; j < len[i]; j++) { + vals.add(testValues[j]); + } + params.put(tp, vals); + List pols = (List) testMethod.invoke(instance, params); + Assert.assertNotNull(pols); + Assert.assertEquals(len[i], pols.size()); + } + } + } + + public void doValidateInteger(DapParamValidator instance, Method m, String paramName, String[] testParams) { + String[] testValues = new String[]{ + "0", + "1", + "2", + "666" + }; + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + + for (String tp : testParams) { + for (String tv : testValues) { + List expected = new ArrayList(); + expected.add(tv); + params.put(tp, expected); + + List actual = (List) m.invoke(instance, params); + + Assert.assertNotNull(actual); + Assert.assertEquals(1, actual.size()); + Integer i = actual.get(0); + Assert.assertEquals(tv, i.toString()); + } + } + + // test multiple values + params.clear(); + List expected = new ArrayList(); + for (String e : testValues) { + expected.add(e); + } + params.put(paramName, expected); + List actual = (List) m.invoke(instance, params); + Assert.assertNotNull(actual); + Assert.assertEquals(expected.size(), actual.size()); + for (String e : expected) { + Integer ei = new Integer(e); + Assert.assertTrue(actual.contains(ei)); + } + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + public void doValidateNumeric(DapParamValidator instance, Method m, String paramName, String[] testParams) { + String LB = "12.3 +Inf"; + String UB = "-Inf 34.5"; + String OPEN = "-Inf +Inf"; + String SCALAR = "1.0 1.0"; + String[] testValues = new String[]{ + "12.3 34.5", + "1.23e1 3.45e1", + "1.23E1 3.45E1", + SCALAR, + LB, + UB, + OPEN + }; + try { + Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); + + for (String tp : testParams) { + for (String tv : testValues) { + List vals = new ArrayList(); + vals.add(tv); + params.put(tp, vals); + + List ranges = (List) m.invoke(instance, params); + + Assert.assertNotNull(ranges); + Assert.assertEquals(1, ranges.size()); + DoubleInterval r = ranges.get(0); + + if (tv == SCALAR) { + Assert.assertEquals(1.0, r.getLower(), 0.001); + Assert.assertEquals(1.0, r.getUpper(), 0.001); + } else if (tv == LB) { + Assert.assertEquals(12.3, r.getLower(), 0.001); + Assert.assertTrue(r.getUpper().isInfinite()); + } else if (tv == UB) { + Assert.assertTrue(r.getLower().isInfinite()); + Assert.assertEquals(34.5, r.getUpper(), 0.001); + } else if (tv == OPEN) { + Assert.assertTrue(r.getLower().isInfinite()); + Assert.assertTrue(r.getUpper().isInfinite()); + } else { + Assert.assertEquals(12.3, r.getLower(), 0.001); + Assert.assertEquals(34.5, r.getUpper(), 0.001); + } + } + } + + // test multiple values + params.clear(); + List vals = new ArrayList(); + for (int i = 0; i < 3; i++) { + vals.add(testValues[i]); + } + params.put(paramName, vals); + List ranges = (List) m.invoke(instance, params); + Assert.assertNotNull(ranges); + Assert.assertEquals(3, ranges.size()); + for (DoubleInterval r : ranges) { + Assert.assertEquals(12.3, r.getLower(), 0.001); + Assert.assertEquals(34.5, r.getUpper(), 0.001); + } + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + +} diff --git a/opencadc.gradle b/opencadc.gradle index e7fb17c5..25e63315 100644 --- a/opencadc.gradle +++ b/opencadc.gradle @@ -1,10 +1,12 @@ configurations { checkstyleDep + intTestCompile.extendsFrom testCompile + intTestRuntime.extendsFrom testRuntime } dependencies { testCompile 'com.puppycrawl.tools:checkstyle:8.2' - checkstyleDep 'org.opencadc:cadc-quality:1.+' + checkstyleDep 'org.opencadc:cadc-quality:[1.0,)' } checkstyle { @@ -14,6 +16,20 @@ checkstyle { sourceSets = [] } +sourceSets { + test { + resources.srcDirs += 'src/test/resources' + } + intTest { + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } + resources.srcDir file('src/intTest/resources') + resources.srcDirs += new File(System.getenv('A') + '/test-certificates/') + } +} + // Temporary work around for issue https://github.com/gradle/gradle/issues/881 - // gradle not displaying fail build status when warnings reported --> @@ -28,6 +44,25 @@ tasks.withType(Checkstyle).each { checkstyleTask -> } } +tasks.withType(Test) { + // reset the report destinations so that intTests go to their own page + //reports.html.destination = file("${reporting.baseDir}/${name}") + reports.html.destination = file(reporting.baseDir.getAbsolutePath() + '/' + name) + + // Assign all Java system properties from + // the command line to the tests + systemProperties System.properties +} + +task intTest(type: Test) { + // set the configuration context + testClassesDirs = sourceSets.intTest.output.classesDirs + classpath = sourceSets.intTest.runtimeClasspath + + // run the tests always + outputs.upToDateWhen { false } +} + test { testLogging { events "PASSED", "FAILED", "SKIPPED" @@ -35,3 +70,9 @@ test { } } +intTest { + testLogging { + events "PASSED", "FAILED", "SKIPPED" + // "STARTED", + } +}