From a15a9131fb36c2e539dd3babf4fc161b5db702fc Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Tue, 2 May 2023 15:47:33 -0700 Subject: [PATCH 1/6] cadc-dali: port caom2 polygon validation and calculation code --- cadc-dali/build.gradle | 2 +- .../ca/nrc/cadc/dali/CartesianTransform.java | 348 +++++++++++++++++ .../cadc/dali/InvalidPolygonException.java | 80 ++++ .../main/java/ca/nrc/cadc/dali/Polygon.java | 356 +++++++++++++++++- .../java/ca/nrc/cadc/dali/PolygonTest.java | 239 ++++++++++++ 5 files changed, 1015 insertions(+), 10 deletions(-) create mode 100644 cadc-dali/src/main/java/ca/nrc/cadc/dali/CartesianTransform.java create mode 100644 cadc-dali/src/main/java/ca/nrc/cadc/dali/InvalidPolygonException.java create mode 100644 cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java diff --git a/cadc-dali/build.gradle b/cadc-dali/build.gradle index 220822cd..d7151a14 100644 --- a/cadc-dali/build.gradle +++ b/cadc-dali/build.gradle @@ -14,7 +14,7 @@ sourceCompatibility = 1.8 group = 'org.opencadc' -version = '1.2.17' +version = '1.2.18' description = 'OpenCADC DALI library' def git_url = 'https://github.com/opencadc/dal' diff --git a/cadc-dali/src/main/java/ca/nrc/cadc/dali/CartesianTransform.java b/cadc-dali/src/main/java/ca/nrc/cadc/dali/CartesianTransform.java new file mode 100644 index 00000000..27a908a4 --- /dev/null +++ b/cadc-dali/src/main/java/ca/nrc/cadc/dali/CartesianTransform.java @@ -0,0 +1,348 @@ +/* +************************************************************************ +******************* 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 ca.nrc.cadc.dali; + +import org.apache.log4j.Logger; + +// package access so only usable from Polygon (for now) +class CartesianTransform { + private static final Logger log = Logger.getLogger(CartesianTransform.class); + + // the negative X axis + static final double[] NEG_X_AXIS = new double[] { -1.0, 0.0, 0.0 }; + + // the positive X axis + static final double[] POS_X_AXIS = new double[] { 1.0, 0.0, 0.0 }; + + public static final String X = "x"; + public static final String Y = "y"; + public static final String Z = "z"; + + // some tests mess with these + double angle; + String axis; + + private CartesianTransform() { + } + + public static CartesianTransform getTransform(Polygon poly) { + return getTransform(poly, false); + } + + public static CartesianTransform getTransform(Polygon poly, boolean force) { + double[] cube = getBoundingCube(poly, null); + return getTransform(cube, force); + } + + public static CartesianTransform getTransform(Circle c) { + return getTransform(c, false); + } + + public static CartesianTransform getTransform(Circle c, boolean force) { + double[] xyz = CartesianTransform.toUnitSphere(c.getCenter().getLongitude(), c.getCenter().getLatitude()); + double[] cube = new double[] { + xyz[0], xyz[0], + xyz[1], xyz[1], + xyz[2], xyz[2] + }; + return getTransform(cube, force); + } + + public static CartesianTransform getTransform(double[] cube, + boolean force) { + double x1 = cube[0]; + double x2 = cube[1]; + double y1 = cube[2]; + double y2 = cube[3]; + double z1 = cube[4]; + double z2 = cube[5]; + // log.debug("getTransform: bounding cube = " + x1+ ":" + x2 + " " + y1 + // + ":" + y2 + " " + z1 + ":" + z2); + + double cx = 0.5 * (x1 + x2); + double cz = 0.5 * (z1 + z2); + // log.debug("getTransform: bounding cube center = " + cx + " " + cy + " + // " + cz); + + if (Math.abs(cz) > 0.02 || force) { + CartesianTransform trans = new CartesianTransform(); + trans.axis = CartesianTransform.Y; + // project vector in X-Z plane and normalise + double mv1 = Math.sqrt(cx * cx + cz * cz); + double[] v1 = { cx / mv1, 0, cz / mv1 }; + // acos is only valid for 0 to PI + if (cz > 0.0) { // north + // angle to +X axis, then add 180 degrees + double[] v2 = { 1, 0, 0 }; + double cosa = dotProduct(v1, v2); + trans.angle = Math.acos(cosa) + Math.PI; + } else { // south + // directly get angle to -X axis + double[] v2 = { -1, 0, 0 }; + double cosa = dotProduct(v1, v2); + trans.angle = Math.acos(cosa); + } + log.debug("off equator: " + trans); + return trans; + } + if (y1 <= 0.0 && y2 >= 0.0 && x1 > 0.0) { + CartesianTransform trans = new CartesianTransform(); + trans.angle = Math.PI; + trans.axis = CartesianTransform.Z; + log.debug("straddling meridan at equator: " + trans); + return trans; + } + return new CartesianTransform(); + } + + public CartesianTransform getInverseTransform() { + if (isNull()) { + return this; + } + CartesianTransform ret = new CartesianTransform(); + ret.axis = axis; + ret.angle = -1.0 * angle; + return ret; + } + + @Override + public String toString() { + return "CartesianTransform[" + axis + "," + angle + "]"; + } + + public boolean isNull() { + return axis == null; + } + + /** + * Convert long,lat coordinates to cartesian coordinates on the unit sphere. + * NOTE: this uses a right-handed unit sphere but looking from the outside, + * which is opposite to the normal astro convention of looking from the + * center (which means theta goes in the other direction in astronomy), but + * as long as the opposite conversion is done with toLongLat that is just + * fine. + * + * @param longitude + * @param latitude + * @return a double[3] + */ + public static double[] toUnitSphere(double longitude, double latitude) { + double[] ret = new double[3]; + double theta = Math.toRadians(longitude); + double phi = Math.toRadians(90.0 - latitude); + ret[0] = Math.cos(theta) * Math.sin(phi); + ret[1] = Math.sin(theta) * Math.sin(phi); + ret[2] = Math.cos(phi); + return ret; + } + + /** + * Convert cartesian points on the unit sphere to long,lat. + * + * @param x + * @param y + * @param z + * @return a double[2] + */ + public static double[] toLongLat(double x, double y, double z) { + double[] ret = new double[2]; + if ((x == 0.0 && y == 0.0) || z == 1.0 || z == -1.0) { + ret[0] = 0.0; + } else { + ret[0] = Math.toDegrees(Math.atan2(y, x)); + } + if (ret[0] < 0.0) { + ret[0] += 360.0; + } + if (z > 1.0) { + z = 1.0; + } else if (z < -1.0) { + z = -1.0; + } + ret[1] = 90.0 - Math.toDegrees(Math.acos(z)); + return ret; + } + + public Point transform(Point p) { + if (isNull()) { + return p; + } + + double[] p2 = transformPoint(p); + return new Point(p2[0], p2[1]); + } + + public Polygon transform(Polygon p) { + if (isNull()) { + return p; + } + + Polygon ret = new Polygon(); + for (Point v : p.getVertices()) { + ret.getVertices().add(transform(v)); + } + return ret; + } + + // impl for above methods + private double[] transformPoint(Point p) { + double[] xyz = CartesianTransform.toUnitSphere(p.getLongitude(), p.getLatitude()); + double[] dp = rotate(xyz); + return CartesianTransform.toLongLat(dp[0], dp[1], dp[2]); + } + + public static double[] getBoundingCube(Polygon poly, double[] cube) { + // x1, x2, y1, y2, z1, z2 + if (cube == null) { + cube = new double[6]; + cube[0] = Double.POSITIVE_INFINITY; + cube[1] = Double.NEGATIVE_INFINITY; + cube[2] = Double.POSITIVE_INFINITY; + cube[3] = Double.NEGATIVE_INFINITY; + cube[4] = Double.POSITIVE_INFINITY; + cube[5] = Double.NEGATIVE_INFINITY; + } + for (Point v : poly.getVertices()) { + double[] xyz = CartesianTransform.toUnitSphere(v.getLongitude(), + v.getLatitude()); + cube[0] = Math.min(cube[0], xyz[0]); + cube[1] = Math.max(cube[1], xyz[0]); + + cube[2] = Math.min(cube[2], xyz[1]); + cube[3] = Math.max(cube[3], xyz[1]); + + cube[4] = Math.min(cube[4], xyz[2]); + cube[5] = Math.max(cube[5], xyz[2]); + } + // fix X bounds assuming smallish polygons and cubes + if (cube[2] < 0.0 && 0.0 < cube[3] && cube[4] < 0.0 && 0.0 < cube[5]) { + if (cube[0] > 0.0) { + cube[1] = 1.01; // slightly outside sphere + } else if (cube[1] < 0.0) { + cube[0] = -1.01; // slightly outside sphere + } + } + // fix Y bounds assuming smallish polygons and cubes + if (cube[0] < 0.0 && 0.0 < cube[1] && cube[4] < 0.0 && 0.0 < cube[5]) { + if (cube[2] > 0.0) { + cube[3] = 1.01; // slightly outside sphere + } else if (cube[3] < 0.0) { + cube[2] = -1.01; // slightly outside sphere + } + } + // fix Z bounds assuming smallish polygons and cubes + if (cube[0] < 0.0 && 0.0 < cube[1] && cube[2] < 0.0 && 0.0 < cube[3]) { + if (cube[4] > 0.0) { + cube[5] = 1.01; // slightly outside sphere + } else if (cube[5] < 0.0) { + cube[4] = -1.01; // slightly outside sphere + } + } + return cube; + } + + private static double dotProduct(double[] v1, double[] v2) { + return (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]); + } + + public double[] rotate(double[] p) { + if (Y.equals(axis)) { + return yrotate(p); + } + + if (Z.equals(axis)) { + return zrotate(p); + } + + throw new IllegalStateException("unknown axis: " + axis); + } + + // rotation about the y-axis + private double[] yrotate(double[] p) { + double[] ret = new double[3]; + double cr = Math.cos(angle); + double sr = Math.sin(angle); + ret[0] = cr * p[0] + sr * p[2]; + ret[1] = p[1]; + ret[2] = -1.0 * sr * p[0] + cr * p[2]; + return ret; + } + + // rotation about the z-axis + private double[] zrotate(double[] p) { + double[] ret = new double[3]; + double cr = Math.cos(angle); + double sr = Math.sin(angle); + ret[0] = cr * p[0] + -1 * sr * p[1]; + ret[1] = sr * p[0] + cr * p[1]; + ret[2] = p[2]; + return ret; + } +} diff --git a/cadc-dali/src/main/java/ca/nrc/cadc/dali/InvalidPolygonException.java b/cadc-dali/src/main/java/ca/nrc/cadc/dali/InvalidPolygonException.java new file mode 100644 index 00000000..21246a09 --- /dev/null +++ b/cadc-dali/src/main/java/ca/nrc/cadc/dali/InvalidPolygonException.java @@ -0,0 +1,80 @@ +/* +************************************************************************ +******************* 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 : +* . +* +************************************************************************ +*/ + +package ca.nrc.cadc.dali; + +/** + * Exception for a polygon that is invalid according to the DALI specification. + * + * @author pdowler + */ +public class InvalidPolygonException extends Exception { + + public InvalidPolygonException(String msg) { + super(msg); + } +} diff --git a/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java b/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java index 36e8e0f0..ff1ca1f7 100644 --- a/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java +++ b/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * -* (c) 2019. (c) 2019. +* (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 @@ -70,14 +70,25 @@ package ca.nrc.cadc.dali; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import org.apache.log4j.Logger; /** - * + * DALI polygon class with port of CAOM-2.4 polygon validation code. + * * @author pdowler */ public class Polygon implements Shape { - private List vertices = new ArrayList(); + private static final Logger log = Logger.getLogger(Polygon.class); + + private final List points = new ArrayList(); + + // lazily computed + private transient Point center; + private transient Double area; + private transient Circle minimumSpanningCircle; + private transient Boolean ccw; public Polygon() { } @@ -86,7 +97,7 @@ public Polygon() { public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Polygon["); - for (Point v : vertices) { + for (Point v : points) { sb.append(v.getLongitude()).append(" ").append(v.getLatitude()).append(" "); } sb.setCharAt(sb.length() - 1, ']'); @@ -94,7 +105,7 @@ public String toString() { } public List getVertices() { - return vertices; + return points; } @Override @@ -104,12 +115,12 @@ public boolean equals(Object obj) { } Polygon rhs = (Polygon) obj; - if (this.vertices.size() != rhs.vertices.size()) { + if (this.points.size() != rhs.points.size()) { return false; } - for (int i = 0; i < vertices.size(); i++) { - Point tp = this.vertices.get(i); - Point rp = rhs.vertices.get(i); + for (int i = 0; i < points.size(); i++) { + Point tp = this.points.get(i); + Point rp = rhs.points.get(i); if (!tp.equals(rp)) { return false; } @@ -117,4 +128,331 @@ public boolean equals(Object obj) { return true; } + /** + * Validate this polygon for conformance to IVOA DALI polygon rules. + * @throws InvalidPolygonException violation of DALI spec + */ + public final void validate() throws InvalidPolygonException { + //validatePoints(); + validateSegments(); + initProps(); + // DALI polygons are always CCW + // unsupported: if we detect CW here it is equivalent to the region + // outside with area = 4*pi - area and larger than half the sphere + if (!ccw) { + throw new InvalidPolygonException("clockwise winding direction"); + } + } + + public Point getCenter() { + if (center == null) { + initProps(); + } + return center; + } + + public double getArea() { + if (area == null) { + initProps(); + } + return area; + } + + public double getSize() { + if (minimumSpanningCircle == null) { + initProps(); + } + return 2.0 * minimumSpanningCircle.getRadius(); + } + + public Circle getMinimumSpanningCircle() { + if (minimumSpanningCircle == null) { + initProps(); + } + return minimumSpanningCircle; + } + + private void initProps() { + PolygonProperties pp = computePolygonProperties(); + this.area = pp.area; + this.center = pp.center; + this.minimumSpanningCircle = pp.minSpanCircle; + this.ccw = pp.windCounterClockwise; + } + + private static class PolygonProperties { + boolean windCounterClockwise; + Double area; + Point center; + Circle minSpanCircle; + } + + private PolygonProperties computePolygonProperties() { + // log.debug("computePolygonProperties: " + poly); + // the transform needed for computing things in long/lat using cartesian + // approximation + CartesianTransform trans = CartesianTransform.getTransform(this); + Polygon tpoly = trans.transform(this); + + // algorithm from + // http://astronomy.swin.edu.au/~pbourke/geometry/polyarea/ + double a = 0.0; + double cx = 0.0; + double cy = 0.0; + int lastMoveTo = 0; // start of simple polygon + Iterator pi = tpoly.getVertices().iterator(); + Point start = pi.next(); + Point v1 = start; + while (pi.hasNext()) { + Point v2 = pi.next(); + //log.warn("props: " + v1 + " " + v2); + double tmp = v1.getLongitude() * v2.getLatitude() - v2.getLongitude() * v1.getLatitude(); + a += tmp; + cx += (v1.getLongitude() + v2.getLongitude()) * tmp; + cy += (v1.getLatitude() + v2.getLatitude()) * tmp; + v1 = v2; + if (!pi.hasNext()) { + v2 = start; + //log.warn("props/implicit close: " + v1 + " " + v2); + tmp = v1.getLongitude() * v2.getLatitude() - v2.getLongitude() * v1.getLatitude(); + a += tmp; + cx += (v1.getLongitude() + v2.getLongitude()) * tmp; + cy += (v1.getLatitude() + v2.getLatitude()) * tmp; + } + } + + //log.warn("raw props: " + cx + "," + cy + " a=" + a); + a *= 0.5; + cx = cx / (6.0 * a); + cy = cy / (6.0 * a); + //log.warn("props: " + cx + "," + cy + " a=" + a); + + + // quick and dirty minimum spanning circle computation + double d = 0.0; + Point e1 = null; + Point e2 = null; + for (int i = 0; i < tpoly.getVertices().size(); i++) { + Point vi = tpoly.getVertices().get(i); + for (int j = i + 1; j < tpoly.getVertices().size(); j++) { + Point vj = tpoly.getVertices().get(j); + double d1 = vi.getLongitude() - vj.getLongitude(); + double d2 = vi.getLatitude() - vj.getLatitude(); + double dd = Math.sqrt(d1 * d1 + d2 * d2); + if (dd > d) { + d = dd; + e1 = vi; + e2 = vj; + + } + } + } + + PolygonProperties ret = new PolygonProperties(); + ret.windCounterClockwise = (a < 0.0); // RA-DEC increases left-up + //log.warn("a: " + a + " ccw: " + ret.windCounterClockwise); + if (a < 0.0) { + a *= -1.0; + } + ret.area = a; + + CartesianTransform inv = trans.getInverseTransform(); + //log.warn("transform: " + cx + "," + cy + " with " + inv); + ret.center = inv.transform(new Point(cx, cy)); + + // midpoint between vertices + if (e1 != null && e2 != null && d > 0.0) { + Point cen = new Point(0.5 * Math.abs(e1.getLongitude() + e2.getLongitude()), + 0.5 * Math.abs(e1.getLatitude() + e2.getLatitude())); + Point mscc = inv.transform(cen); + ret.minSpanCircle = new Circle(mscc, d / 2.0); + } + + return ret; + } + + /* not needed because dali.Point checks coordinate range + private void validatePoints() throws InvalidPolygonException { + if (points.size() < 3) { + throw new InvalidPolygonException( + "polygon has " + points.size() + " points: minimum 3"); + } + StringBuilder msg = new StringBuilder("invalid Polygon: "); + for (Point p : points) { + boolean flong = p.getLongitude() < 0.0 || p.getLongitude() > 360.0; + boolean flat = p.getLatitude() < -90.0 || p.getLatitude() > 90.0; + if (flong && flat) { + msg.append("longitude,latitude not in [0,360],[-90,90]: ").append(p.getLongitude()).append(",").append(p.getLatitude()); + } else if (flong) { + msg.append("longitude not in [0,360]: ").append(p.getLongitude()); + } else if (flat) { + msg.append("latitude not in [-90,90]: ").append(p.getLatitude()); + } + if (flong || flat) { + throw new InvalidPolygonException(msg.toString()); + } + } + } + */ + + private void validateSegments() throws InvalidPolygonException { + CartesianTransform trans = CartesianTransform.getTransform(this); + Polygon tpoly = trans.transform(this); + + Iterator vi = tpoly.getVertices().iterator(); + List tsegs = new ArrayList<>(); + List psegs = tsegs; + Point start = vi.next(); + Point v1 = start; + while (vi.hasNext()) { + Point v2 = vi.next(); + Segment s = new Segment(v1, v2); + //log.warn("[validateSegments] tseg: " + s); + tsegs.add(s); + v1 = v2; + if (!vi.hasNext()) { + v2 = start; + s = new Segment(v1, v2); + //log.warn("[validateSegments] implicit tseg: " + s); + tsegs.add(s); + } + } + if (this != tpoly) { + // make segments with orig coords for reporting + vi = this.getVertices().iterator(); + psegs = new ArrayList<>(); + start = vi.next(); + v1 = start; + while (vi.hasNext()) { + Point v2 = vi.next(); + Segment s = new Segment(v1, v2); + //log.warn("[validateSegments] pseg: " + s); + psegs.add(s); + v1 = v2; + if (!vi.hasNext()) { + v2 = start; + s = new Segment(v1, v2); + //log.warn("[validateSegments] implicit pseg: " + s); + psegs.add(s); + } + + } + } + intersects(tsegs, psegs); + } + + private static void intersects(List transSegments, List origSegments) throws InvalidPolygonException { + for (int i = 0; i < transSegments.size(); i++) { + Segment s1 = transSegments.get(i); + for (int j = 0; j < transSegments.size(); j++) { + if (i != j) { + Segment s2 = transSegments.get(j); + if (intersects(s1, s2)) { + Segment r1 = origSegments.get(i); + Segment r2 = origSegments.get(j); + throw new InvalidPolygonException("invalid Polygon: segment intersect " + r1 + " vs " + r2); + } + } + } + } + } + + private static boolean intersects(Segment ab, Segment cd) { + //log.debug("intersects: " + ab + " vs " + cd); + // rden = (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) + double den = (ab.v2.getLongitude() - ab.v1.getLongitude()) * (cd.v2.getLatitude() - cd.v1.getLatitude()) + - (ab.v2.getLatitude() - ab.v1.getLatitude()) * (cd.v2.getLongitude() - cd.v1.getLongitude()); + //log.debug("den = " + den); + + //rnum = (Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy) + double rnum = (ab.v1.getLatitude() - cd.v1.getLatitude()) * (cd.v2.getLongitude() - cd.v1.getLongitude()) + - (ab.v1.getLongitude() - cd.v1.getLongitude()) * (cd.v2.getLatitude() - cd.v1.getLatitude()); + //log.debug("rnum = " + rnum); + + if (Math.abs(den) < 1.0e-12) { //(den == 0.0) + if (Math.abs(rnum) < 1.0e-12) { //(rnum == 0.0) + // colinear: check overlap on one axis + if (ab.v2 == cd.v1 || ab.v1 == cd.v2) { + return false; // end-to-end + } + double len1 = ab.lengthSquared(); + double len2 = cd.lengthSquared(); + Segment s = ab; + if (len2 > len1) { + s = cd; // the longer one + } + double dx = Math.abs(s.v1.getLongitude() - s.v2.getLongitude()); + double dy = Math.abs(s.v1.getLatitude() - s.v2.getLatitude()); + if (dx > dy) { // more horizontal = project to coordX + if (ab.v2.getLongitude() < cd.v1.getLongitude()) { + return false; // ab left of cd + } + if (ab.v1.getLongitude() > cd.v2.getLongitude()) { + return false; // ab right of cd + } + } else { // more vertical = project to coordY + if (ab.v2.getLatitude() < cd.v1.getLatitude()) { + return false; // ab below cd + } + if (ab.v1.getLatitude() > cd.v2.getLatitude()) { + return false; // ab above cd + } + } + return true; // overlapping + } + return false; // just parallel + } + + double r = rnum / den; + //log.debug("radius = " + radius); + // no intersect, =0 or 1 means the ends touch, which is normal but pg_sphere doesn't like it + //if (radius < 0.0 || radius > 1.0) + if (r <= 0.0 || r >= 1.0) { + return false; + } + + //snum = (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) + double snum = (ab.v1.getLatitude() - cd.v1.getLatitude()) * (ab.v2.getLongitude() - ab.v1.getLongitude()) + - (ab.v1.getLongitude() - cd.v1.getLongitude()) * (ab.v2.getLatitude() - ab.v1.getLatitude()); + //log.debug("snum = " + snum); + + double s = snum / den; + //log.debug("s = " + s); + //if (s < 0.0 || s > 1.0) + if (s <= 0.0 || s >= 1.0) { + return false; // no intersect, =0 or 1 means the ends touch, which is normal + } + + // radius in [0,1] and s in [0,1] = intersects + return true; + } + + private static class Segment { + + Point v1; + Point v2; + + Segment(Point v1, Point v2) { + this.v1 = v1; + this.v2 = v2; + } + + double length() { + return Math.sqrt(lengthSquared()); + } + + double lengthSquared() { + return distanceSquared(v1, v2); + } + + @Override + public String toString() { + return "Segment[" + v1.getLongitude() + "," + v1.getLatitude() + ":" + v2.getLongitude() + "," + v2.getLatitude() + "]"; + } + } + + private static double distanceSquared(Point v1, Point v2) { + return (v1.getLongitude() - v2.getLongitude()) * (v1.getLongitude() - v2.getLongitude()) + + (v1.getLatitude() - v2.getLatitude()) * (v1.getLatitude() - v2.getLatitude()); + } } diff --git a/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java b/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java new file mode 100644 index 00000000..8a27e655 --- /dev/null +++ b/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java @@ -0,0 +1,239 @@ +/* +************************************************************************ +******************* 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 : +* . +* +************************************************************************ + */ + +package ca.nrc.cadc.dali; + +import ca.nrc.cadc.util.Log4jInit; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; + +/** + * Port of CAOM-2.4 polygon validation tests. + * + * @author pdowler + */ +public class PolygonTest { + + private static final Logger log = Logger.getLogger(PolygonTest.class); + + static { + Log4jInit.setLevel("ca.nrc.cadc.dali", Level.INFO); + } + + public PolygonTest() { + } + + @Test + public void testValidPolygon() { + try { + Polygon p = new Polygon(); + p.getVertices().add(new Point(2.0, 2.0)); + p.getVertices().add(new Point(1.0, 4.0)); + p.getVertices().add(new Point(3.0, 3.0)); + + p.validate(); + + Point c = p.getCenter(); + Assert.assertNotNull(c); + Assert.assertEquals(2.0, c.getLongitude(), 0.01); + Assert.assertEquals(3.0, c.getLatitude(), 0.01); + + Assert.assertEquals(1.5, p.getArea(), 0.1); + + Circle msc = p.getMinimumSpanningCircle(); + Assert.assertNotNull(msc); + Assert.assertEquals(1.5, msc.getCenter().getLongitude(), 0.01); + Assert.assertEquals(3.0, msc.getCenter().getLatitude(), 0.01); + Assert.assertEquals(1.12, msc.getRadius(), 0.01); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidPolygonFromFootprintPy() { + // footprint.py output + // old: Polygon ICRS 259.006152 60.047132 259.087308 60.087963 259.089760 60.087730 259.132216 60.068435 259.131770 60.067704 259.133299 60.067895 259.174702 60.049127 259.093400 60.010342 259.078635 60.015734 + String[] oldS = "259.006152 60.047132 259.087308 60.087963 259.089760 60.087730 259.132216 60.068435 259.131770 60.067704 259.133299 60.067895 259.174702 60.049127 259.093400 60.010342 259.078635 60.015734".split(" "); + + // cur: Polygon ICRS 259.006152 60.047132 259.078635 60.015734 259.093400 60.010342 259.174702 60.049127 259.133299 60.067895 259.131770 60.067704 259.132216 60.068435 259.089760 60.087730 259.087308 60.087963 + String[] curS = "259.006152 60.047132 259.078635 60.015734 259.093400 60.010342 259.174702 60.049127 259.133299 60.067895 259.131770 60.067704 259.132216 60.068435 259.089760 60.087730 259.087308 60.087963".split(" "); + + String[] test = oldS; + try { + Polygon p = new Polygon(); + for (int i = 0; i < test.length; i += 2) { + double x = Double.parseDouble(test[i]); + double y = Double.parseDouble(test[i + 1]); + p.getVertices().add(new Point(x, y)); + } + + p.validate(); + + log.info("testValidPolygonFromFootprintPy: " + p); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testInvalidLongitude() { + try { + Polygon p = new Polygon(); + p.getVertices().add(new Point(360.0, 2.0)); + p.getVertices().add(new Point(359.0, 4.0)); + p.getVertices().add(new Point(361.0, 3.0)); + + p.validate(); + Assert.fail("expected IllegalArgumentException, created: " + p); + } catch (IllegalArgumentException expected) { + log.info("caught expected: " + expected); + Assert.assertTrue(expected.getMessage().startsWith("invalid longitude")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testInvalidLatitude() { + try { + Polygon p = new Polygon(); + p.getVertices().add(new Point(2.0, 89.0)); + p.getVertices().add(new Point(1.0, 91.0)); + p.getVertices().add(new Point(3.0, 90.0)); + + p.validate(); + Assert.fail("expected IllegalArgumentException, created: " + p); + } catch (IllegalArgumentException expected) { + log.info("caught expected: " + expected); + Assert.assertTrue(expected.getMessage().startsWith("invalid latitude")); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testInvalidPolygonCW() { + try { + Polygon p = new Polygon(); + p.getVertices().add(new Point(2.0, 2.0)); + p.getVertices().add(new Point(3.0, 3.0)); + p.getVertices().add(new Point(1.0, 4.0)); + + p.validate(); + Assert.fail("expected InvalidPolygonException, created: " + p); + } catch (InvalidPolygonException expected) { + log.info("caught expected: " + expected); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testInvalidPolygonSegmentIntersect() { + try { + Polygon poly = new Polygon(); + poly.getVertices().add(new Point(2.0, 2.0)); + poly.getVertices().add(new Point(2.0, 4.0)); + poly.getVertices().add(new Point(4.0, 2.0)); + poly.getVertices().add(new Point(4.0, 4.0)); + + try { + poly.validate(); + Assert.fail("expected InvalidPolygonException - got: " + poly); + } catch (InvalidPolygonException expected) { + log.info("testValidateSegments: butterfly " + expected); + Assert.assertTrue(expected.getMessage().startsWith("invalid Polygon: segment intersect ")); + } + + poly.getVertices().clear(); + poly.getVertices().add(new Point(2.0, 2.0)); + poly.getVertices().add(new Point(2.0, 4.0)); + poly.getVertices().add(new Point(5.0, 4.0)); // extra small loop + poly.getVertices().add(new Point(4.0, 5.0)); + poly.getVertices().add(new Point(4.0, 2.0)); + + try { + poly.validate(); + Assert.fail("expected InvalidPolygonException - got: " + poly); + } catch (InvalidPolygonException expected) { + log.info("testValidateSegments: small loop " + expected); + Assert.assertTrue(expected.getMessage().contains("invalid Polygon: segment intersect ")); + } + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } +} From 91d271e285fde0639b9de5e93665f3a37e7eb661 Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Tue, 2 May 2023 16:08:14 -0700 Subject: [PATCH 2/6] add Polygon.getCounterClockwise() --- .../main/java/ca/nrc/cadc/dali/Polygon.java | 18 +++++++++++++++++- .../java/ca/nrc/cadc/dali/PolygonTest.java | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java b/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java index ff1ca1f7..988d6dc6 100644 --- a/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java +++ b/cadc-dali/src/main/java/ca/nrc/cadc/dali/Polygon.java @@ -75,7 +75,10 @@ import org.apache.log4j.Logger; /** - * DALI polygon class with port of CAOM-2.4 polygon validation code. + * DALI polygon class with port of CAOM-2.4 polygon validation code. Note: the code + * in this class assumes that polygons are small (flat) enough that cartesian math is + * a reasonable approximation and it will give wrong answers for polygons that are too + * large (10s of degrees across??) and especially for those larger than half the sphere. * * @author pdowler */ @@ -130,6 +133,7 @@ public boolean equals(Object obj) { /** * Validate this polygon for conformance to IVOA DALI polygon rules. + * * @throws InvalidPolygonException violation of DALI spec */ public final void validate() throws InvalidPolygonException { @@ -144,6 +148,18 @@ public final void validate() throws InvalidPolygonException { } } + /** + * Check if the polygon is counter-clockwise. + * + * @return true if counter-clockwise (valid), false is clockwise (invalid) + */ + public boolean getCounterClockwise() { + if (ccw == null) { + initProps(); + } + return ccw; + } + public Point getCenter() { if (center == null) { initProps(); diff --git a/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java b/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java index 8a27e655..bde8b8a8 100644 --- a/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java +++ b/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java @@ -99,6 +99,8 @@ public void testValidPolygon() { p.validate(); + Assert.assertTrue("CCW", p.getCounterClockwise()); + Point c = p.getCenter(); Assert.assertNotNull(c); Assert.assertEquals(2.0, c.getLongitude(), 0.01); @@ -189,6 +191,8 @@ public void testInvalidPolygonCW() { p.getVertices().add(new Point(2.0, 2.0)); p.getVertices().add(new Point(3.0, 3.0)); p.getVertices().add(new Point(1.0, 4.0)); + + Assert.assertFalse("CCW", p.getCounterClockwise()); p.validate(); Assert.fail("expected InvalidPolygonException, created: " + p); From b2f87effb8f54e856435531caeb7aa31bde122c0 Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Thu, 28 Sep 2023 23:29:44 -0700 Subject: [PATCH 3/6] sia2: initial SIA-2.0 container service --- sia2/Dockerfile | 3 + sia2/README.md | 75 +++ sia2/VERSION | 6 + sia2/build.gradle | 54 ++ .../org/opencadc/sia2/SiaQuery2ErrorTest.java | 127 ++++ .../org/opencadc/sia2/SiaQuery2GetTest.java | 125 ++++ .../org/opencadc/sia2/VOTableHandler.java | 131 ++++ .../opencadc/sia2/VosiAvailabilityTest.java | 83 +++ .../opencadc/sia2/VosiCapabilitiesTest.java | 83 +++ .../resources/SyncTest-ERROR-BAND.properties | 2 + .../resources/SyncTest-ERROR-CALIB.properties | 2 + .../SyncTest-ERROR-DPTYPE.properties | 2 + .../SyncTest-ERROR-EXPTIME.properties | 2 + .../resources/SyncTest-ERROR-FOV.properties | 2 + .../resources/SyncTest-ERROR-POL.properties | 2 + .../resources/SyncTest-ERROR-POS.properties | 2 + .../SyncTest-ERROR-SPATRES.properties | 2 + .../SyncTest-ERROR-SPECRP.properties | 2 + .../resources/SyncTest-ERROR-TIME.properties | 2 + .../SyncTest-ERROR-TIMERES.properties | 2 + .../SyncTest-OK-BAND-scalar.properties | 3 + .../resources/SyncTest-OK-BAND.properties | 3 + .../resources/SyncTest-OK-CALIB.properties | 4 + .../SyncTest-OK-COLLECTION.properties | 3 + .../resources/SyncTest-OK-DPTYPE.properties | 3 + .../resources/SyncTest-OK-EXPTIME.properties | 3 + .../resources/SyncTest-OK-FACILITY.properties | 3 + .../resources/SyncTest-OK-FORMAT.properties | 3 + .../resources/SyncTest-OK-FOV.properties | 3 + .../resources/SyncTest-OK-ID.properties | 3 + .../SyncTest-OK-INSTRUMENT.properties | 3 + .../resources/SyncTest-OK-POL.properties | 5 + .../SyncTest-OK-POS-Circle.properties | 3 + .../SyncTest-OK-POS-OpenRange.properties | 3 + .../SyncTest-OK-POS-Polygon.properties | 3 + .../SyncTest-OK-POS-Range.properties | 3 + .../resources/SyncTest-OK-SPATRES.properties | 3 + .../resources/SyncTest-OK-SPECRP.properties | 3 + .../resources/SyncTest-OK-TARGET.properties | 3 + .../SyncTest-OK-TIME-scalar.properties | 3 + .../resources/SyncTest-OK-TIME.properties | 3 + .../resources/SyncTest-OK-TIMERES.properties | 3 + .../resources/SyncTest-OK-multiple.properties | 21 + sia2/src/intTest/resources/VOTable-v1.3.xsd | 568 ++++++++++++++++++ ...sable-SyncTest-ERROR-COLLECTION.properties | 2 + ...disable-SyncTest-ERROR-FACILITY.properties | 2 + .../disable-SyncTest-ERROR-FORMAT.properties | 2 + .../disable-SyncTest-ERROR-ID.properties | 2 + ...sable-SyncTest-ERROR-INSTRUMENT.properties | 2 + .../disable-SyncTest-ERROR-TARGET.properties | 2 + .../org/opencadc/sia2/AdqlQueryGenerator.java | 354 +++++++++++ .../org/opencadc/sia2/QueryJobManager.java | 111 ++++ .../opencadc/sia2/ServiceAvailability.java | 219 +++++++ .../java/org/opencadc/sia2/SiaConfig.java | 156 +++++ .../org/opencadc/sia2/SiaParamValidator.java | 170 ++++++ .../java/org/opencadc/sia2/SiaRunner.java | 229 +++++++ .../java/org/opencadc/sia2/UsageError.java | 82 +++ sia2/src/main/webapp/META-INF/context.xml | 18 + sia2/src/main/webapp/WEB-INF/web.xml | 112 ++++ sia2/src/main/webapp/capabilities.xml | 67 +++ sia2/src/main/webapp/index.html | 165 +++++ sia2/src/main/webapp/service.json | 369 ++++++++++++ .../opencadc/sia2/AdqlQueryGeneratorTest.java | 287 +++++++++ .../opencadc/sia2/SiaParamValidatorTest.java | 495 +++++++++++++++ 64 files changed, 4213 insertions(+) create mode 100644 sia2/Dockerfile create mode 100644 sia2/README.md create mode 100644 sia2/VERSION create mode 100644 sia2/build.gradle create mode 100644 sia2/src/intTest/java/org/opencadc/sia2/SiaQuery2ErrorTest.java create mode 100644 sia2/src/intTest/java/org/opencadc/sia2/SiaQuery2GetTest.java create mode 100644 sia2/src/intTest/java/org/opencadc/sia2/VOTableHandler.java create mode 100644 sia2/src/intTest/java/org/opencadc/sia2/VosiAvailabilityTest.java create mode 100644 sia2/src/intTest/java/org/opencadc/sia2/VosiCapabilitiesTest.java create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-BAND.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-CALIB.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-DPTYPE.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-EXPTIME.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-FOV.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-POL.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-POS.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-SPATRES.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-SPECRP.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-TIME.properties create mode 100644 sia2/src/intTest/resources/SyncTest-ERROR-TIMERES.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-BAND-scalar.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-BAND.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-CALIB.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-COLLECTION.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-DPTYPE.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-EXPTIME.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-FACILITY.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-FORMAT.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-FOV.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-ID.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-INSTRUMENT.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-POL.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-POS-Circle.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-POS-OpenRange.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-POS-Polygon.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-POS-Range.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-SPATRES.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-SPECRP.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-TARGET.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-TIME-scalar.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-TIME.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-TIMERES.properties create mode 100644 sia2/src/intTest/resources/SyncTest-OK-multiple.properties create mode 100644 sia2/src/intTest/resources/VOTable-v1.3.xsd create mode 100644 sia2/src/intTest/resources/disable-SyncTest-ERROR-COLLECTION.properties create mode 100644 sia2/src/intTest/resources/disable-SyncTest-ERROR-FACILITY.properties create mode 100644 sia2/src/intTest/resources/disable-SyncTest-ERROR-FORMAT.properties create mode 100644 sia2/src/intTest/resources/disable-SyncTest-ERROR-ID.properties create mode 100644 sia2/src/intTest/resources/disable-SyncTest-ERROR-INSTRUMENT.properties create mode 100644 sia2/src/intTest/resources/disable-SyncTest-ERROR-TARGET.properties create mode 100644 sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java create mode 100644 sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java create mode 100644 sia2/src/main/java/org/opencadc/sia2/ServiceAvailability.java create mode 100644 sia2/src/main/java/org/opencadc/sia2/SiaConfig.java create mode 100644 sia2/src/main/java/org/opencadc/sia2/SiaParamValidator.java create mode 100644 sia2/src/main/java/org/opencadc/sia2/SiaRunner.java create mode 100644 sia2/src/main/java/org/opencadc/sia2/UsageError.java create mode 100644 sia2/src/main/webapp/META-INF/context.xml create mode 100644 sia2/src/main/webapp/WEB-INF/web.xml create mode 100644 sia2/src/main/webapp/capabilities.xml create mode 100644 sia2/src/main/webapp/index.html create mode 100644 sia2/src/main/webapp/service.json create mode 100644 sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java create mode 100644 sia2/src/test/java/org/opencadc/sia2/SiaParamValidatorTest.java diff --git a/sia2/Dockerfile b/sia2/Dockerfile new file mode 100644 index 00000000..8e8876a1 --- /dev/null +++ b/sia2/Dockerfile @@ -0,0 +1,3 @@ +FROM images.opencadc.org/library/cadc-tomcat:1 + +COPY build/libs/sia2.war /usr/share/tomcat/webapps diff --git a/sia2/README.md b/sia2/README.md new file mode 100644 index 00000000..9cd85284 --- /dev/null +++ b/sia2/README.md @@ -0,0 +1,75 @@ +# bifrost + +`sia2` is a [Simple Image Access](https://www.ivoa.net/documents/SIA/) service +that should work with any TAP service that provides an ivoa.ObsCore table (or view). + +## deployment +The `sia2` 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. + +`sia2` 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. + + `sia2` requires one connection pool to store jobs: +``` +# database connection pools +org.opencadc.sia2.uws.maxActive={max connections for jobs pool} +org.opencadc.sia2.uws.username={database username for jobs pool} +org.opencadc.sia2.uws.password={database password for jobs pool} +org.opencadc.sia2.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. + +### sia2.properties +`sia2` must be configured to use a single TAP service to execute queries. +``` +# TAP service +org.opencadc.sia2.queryService = {resourceID or TAP base URL} +``` +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. + +`sia2` will attempt to use the caller's identity to query so that CAOM proprietary metadata +protections are enforced, but the details of this depend on the configured IdentityManager +and local A&A service configuration. + +## building it +``` +gradle clean build +docker build -t sia2 -f Dockerfile . +``` + +## checking it +``` +docker run --rm -it sia2:latest /bin/bash +``` + +## running it +``` +docker run --rm --user tomcat:tomcat --volume=/path/to/external/config:/config:ro --name sia2 sia2:latest +``` diff --git a/sia2/VERSION b/sia2/VERSION new file mode 100644 index 00000000..ff0eb44a --- /dev/null +++ b/sia2/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/sia2/build.gradle b/sia2/build.gradle new file mode 100644 index 00000000..8bf9f0cc --- /dev/null +++ b/sia2/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'maven' + id 'war' + id 'checkstyle' +} + +repositories { + mavenCentral() + mavenLocal() +} + +apply from: '../opencadc.gradle' + +sourceCompatibility = 1.8 + +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.4.6,)' + 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/sia2/src/intTest/java/org/opencadc/sia2/SiaQuery2ErrorTest.java b/sia2/src/intTest/java/org/opencadc/sia2/SiaQuery2ErrorTest.java new file mode 100644 index 00000000..6eb4d22a --- /dev/null +++ b/sia2/src/intTest/java/org/opencadc/sia2/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.sia2; + +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("ca.nrc.cadc.sia", Level.INFO); + Log4jInit.setLevel("ca.nrc.cadc.conformance.uws2", Level.INFO); + } + + public SiaQuery2ErrorTest() { + super(URI.create("ivo://opencadc.org/sia2"), 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/sia2/src/intTest/java/org/opencadc/sia2/SiaQuery2GetTest.java b/sia2/src/intTest/java/org/opencadc/sia2/SiaQuery2GetTest.java new file mode 100644 index 00000000..814ecb5d --- /dev/null +++ b/sia2/src/intTest/java/org/opencadc/sia2/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.sia2; + +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("ca.nrc.cadc.sia", Level.INFO); + Log4jInit.setLevel("ca.nrc.cadc.conformance.uws2", Level.INFO); + } + + public SiaQuery2GetTest() { + super(URI.create("ivo://opencadc.org/sia2"), 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/sia2/src/intTest/java/org/opencadc/sia2/VOTableHandler.java b/sia2/src/intTest/java/org/opencadc/sia2/VOTableHandler.java new file mode 100644 index 00000000..4cfbc662 --- /dev/null +++ b/sia2/src/intTest/java/org/opencadc/sia2/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.sia2; + +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/sia2/src/intTest/java/org/opencadc/sia2/VosiAvailabilityTest.java b/sia2/src/intTest/java/org/opencadc/sia2/VosiAvailabilityTest.java new file mode 100644 index 00000000..2db5db37 --- /dev/null +++ b/sia2/src/intTest/java/org/opencadc/sia2/VosiAvailabilityTest.java @@ -0,0 +1,83 @@ +/* +************************************************************************ +******************* 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.sia2; + +import ca.nrc.cadc.vosi.AvailabilityTest; +import java.net.URI; +import org.apache.log4j.Logger; + +public class VosiAvailabilityTest extends AvailabilityTest { + + private static final Logger log = Logger.getLogger(VosiAvailabilityTest.class); + + public VosiAvailabilityTest() { + super(URI.create("ivo://opencadc.org/sia2")); + } +} diff --git a/sia2/src/intTest/java/org/opencadc/sia2/VosiCapabilitiesTest.java b/sia2/src/intTest/java/org/opencadc/sia2/VosiCapabilitiesTest.java new file mode 100644 index 00000000..851c0b16 --- /dev/null +++ b/sia2/src/intTest/java/org/opencadc/sia2/VosiCapabilitiesTest.java @@ -0,0 +1,83 @@ +/* +************************************************************************ +******************* 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.sia2; + +import ca.nrc.cadc.vosi.CapabilitiesTest; +import java.net.URI; +import org.apache.log4j.Logger; + +public class VosiCapabilitiesTest extends CapabilitiesTest { + + private static final Logger log = Logger.getLogger(VosiCapabilitiesTest.class); + + public VosiCapabilitiesTest() { + super(URI.create("ivo://opencadc.org/sia2")); + } +} diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-BAND.properties b/sia2/src/intTest/resources/SyncTest-ERROR-BAND.properties new file mode 100644 index 00000000..3d73d3a6 --- /dev/null +++ b/sia2/src/intTest/resources/SyncTest-ERROR-BAND.properties @@ -0,0 +1,2 @@ +BAND=invalid +expect:Content-Type=application/x-votable+xml diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-CALIB.properties b/sia2/src/intTest/resources/SyncTest-ERROR-CALIB.properties new file mode 100644 index 00000000..da0cc219 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-ERROR-DPTYPE.properties b/sia2/src/intTest/resources/SyncTest-ERROR-DPTYPE.properties new file mode 100644 index 00000000..cccebf2f --- /dev/null +++ b/sia2/src/intTest/resources/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/sia2/src/intTest/resources/SyncTest-ERROR-EXPTIME.properties b/sia2/src/intTest/resources/SyncTest-ERROR-EXPTIME.properties new file mode 100644 index 00000000..62080f26 --- /dev/null +++ b/sia2/src/intTest/resources/SyncTest-ERROR-EXPTIME.properties @@ -0,0 +1,2 @@ +EXPTIME=invalid +expect:Content-Type=application/x-votable+xml diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-FOV.properties b/sia2/src/intTest/resources/SyncTest-ERROR-FOV.properties new file mode 100644 index 00000000..6c833bae --- /dev/null +++ b/sia2/src/intTest/resources/SyncTest-ERROR-FOV.properties @@ -0,0 +1,2 @@ +FOV=invalid +expect:Content-Type=application/x-votable+xml diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-POL.properties b/sia2/src/intTest/resources/SyncTest-ERROR-POL.properties new file mode 100644 index 00000000..635db9d0 --- /dev/null +++ b/sia2/src/intTest/resources/SyncTest-ERROR-POL.properties @@ -0,0 +1,2 @@ +POL=invalid +expect:Content-Type=application/x-votable+xml diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-POS.properties b/sia2/src/intTest/resources/SyncTest-ERROR-POS.properties new file mode 100644 index 00000000..d1b417c3 --- /dev/null +++ b/sia2/src/intTest/resources/SyncTest-ERROR-POS.properties @@ -0,0 +1,2 @@ +POS=Something bad +expect:Content-Type=application/x-votable+xml diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-SPATRES.properties b/sia2/src/intTest/resources/SyncTest-ERROR-SPATRES.properties new file mode 100644 index 00000000..52d900ce --- /dev/null +++ b/sia2/src/intTest/resources/SyncTest-ERROR-SPATRES.properties @@ -0,0 +1,2 @@ +SPATRES=invalid +expect:Content-Type=application/x-votable+xml diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-SPECRP.properties b/sia2/src/intTest/resources/SyncTest-ERROR-SPECRP.properties new file mode 100644 index 00000000..051ce0ef --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-ERROR-TIME.properties b/sia2/src/intTest/resources/SyncTest-ERROR-TIME.properties new file mode 100644 index 00000000..e89970ce --- /dev/null +++ b/sia2/src/intTest/resources/SyncTest-ERROR-TIME.properties @@ -0,0 +1,2 @@ +TIME=invalid +expect:Content-Type=application/x-votable+xml diff --git a/sia2/src/intTest/resources/SyncTest-ERROR-TIMERES.properties b/sia2/src/intTest/resources/SyncTest-ERROR-TIMERES.properties new file mode 100644 index 00000000..cff741d5 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-BAND-scalar.properties b/sia2/src/intTest/resources/SyncTest-OK-BAND-scalar.properties new file mode 100644 index 00000000..4869f98e --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-BAND.properties b/sia2/src/intTest/resources/SyncTest-OK-BAND.properties new file mode 100644 index 00000000..053a3179 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-CALIB.properties b/sia2/src/intTest/resources/SyncTest-OK-CALIB.properties new file mode 100644 index 00000000..a84d7bef --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-COLLECTION.properties b/sia2/src/intTest/resources/SyncTest-OK-COLLECTION.properties new file mode 100644 index 00000000..d6505ab0 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-DPTYPE.properties b/sia2/src/intTest/resources/SyncTest-OK-DPTYPE.properties new file mode 100644 index 00000000..3eb7f31e --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-EXPTIME.properties b/sia2/src/intTest/resources/SyncTest-OK-EXPTIME.properties new file mode 100644 index 00000000..0c5f0f01 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-FACILITY.properties b/sia2/src/intTest/resources/SyncTest-OK-FACILITY.properties new file mode 100644 index 00000000..85b158f2 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-FORMAT.properties b/sia2/src/intTest/resources/SyncTest-OK-FORMAT.properties new file mode 100644 index 00000000..ffd13ed3 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-FOV.properties b/sia2/src/intTest/resources/SyncTest-OK-FOV.properties new file mode 100644 index 00000000..9dfed6d7 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-ID.properties b/sia2/src/intTest/resources/SyncTest-OK-ID.properties new file mode 100644 index 00000000..4c3de2b4 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-INSTRUMENT.properties b/sia2/src/intTest/resources/SyncTest-OK-INSTRUMENT.properties new file mode 100644 index 00000000..020105eb --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-POL.properties b/sia2/src/intTest/resources/SyncTest-OK-POL.properties new file mode 100644 index 00000000..f49ee244 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-POS-Circle.properties b/sia2/src/intTest/resources/SyncTest-OK-POS-Circle.properties new file mode 100644 index 00000000..a33af980 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-POS-OpenRange.properties b/sia2/src/intTest/resources/SyncTest-OK-POS-OpenRange.properties new file mode 100644 index 00000000..0527a208 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-POS-Polygon.properties b/sia2/src/intTest/resources/SyncTest-OK-POS-Polygon.properties new file mode 100644 index 00000000..e8d4f0a2 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-POS-Range.properties b/sia2/src/intTest/resources/SyncTest-OK-POS-Range.properties new file mode 100644 index 00000000..4fa77cb3 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-SPATRES.properties b/sia2/src/intTest/resources/SyncTest-OK-SPATRES.properties new file mode 100644 index 00000000..757ad926 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-SPECRP.properties b/sia2/src/intTest/resources/SyncTest-OK-SPECRP.properties new file mode 100644 index 00000000..e6fa30e4 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-TARGET.properties b/sia2/src/intTest/resources/SyncTest-OK-TARGET.properties new file mode 100644 index 00000000..e300cd84 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-TIME-scalar.properties b/sia2/src/intTest/resources/SyncTest-OK-TIME-scalar.properties new file mode 100644 index 00000000..7a11f9d0 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-TIME.properties b/sia2/src/intTest/resources/SyncTest-OK-TIME.properties new file mode 100644 index 00000000..e94f0f1c --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-TIMERES.properties b/sia2/src/intTest/resources/SyncTest-OK-TIMERES.properties new file mode 100644 index 00000000..0c9e1922 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/SyncTest-OK-multiple.properties b/sia2/src/intTest/resources/SyncTest-OK-multiple.properties new file mode 100644 index 00000000..e89c0218 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/VOTable-v1.3.xsd b/sia2/src/intTest/resources/VOTable-v1.3.xsd new file mode 100644 index 00000000..1d4aaf3e --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/disable-SyncTest-ERROR-COLLECTION.properties b/sia2/src/intTest/resources/disable-SyncTest-ERROR-COLLECTION.properties new file mode 100644 index 00000000..8710d66a --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/disable-SyncTest-ERROR-FACILITY.properties b/sia2/src/intTest/resources/disable-SyncTest-ERROR-FACILITY.properties new file mode 100644 index 00000000..b83a012f --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/disable-SyncTest-ERROR-FORMAT.properties b/sia2/src/intTest/resources/disable-SyncTest-ERROR-FORMAT.properties new file mode 100644 index 00000000..b18fd5cd --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/disable-SyncTest-ERROR-ID.properties b/sia2/src/intTest/resources/disable-SyncTest-ERROR-ID.properties new file mode 100644 index 00000000..c2feaee9 --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/disable-SyncTest-ERROR-INSTRUMENT.properties b/sia2/src/intTest/resources/disable-SyncTest-ERROR-INSTRUMENT.properties new file mode 100644 index 00000000..c041ae4b --- /dev/null +++ b/sia2/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/sia2/src/intTest/resources/disable-SyncTest-ERROR-TARGET.properties b/sia2/src/intTest/resources/disable-SyncTest-ERROR-TARGET.properties new file mode 100644 index 00000000..eb5b3840 --- /dev/null +++ b/sia2/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/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java b/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java new file mode 100644 index 00000000..4323d668 --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java @@ -0,0 +1,354 @@ +/* +************************************************************************ +******************* 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.sia2; + +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 Map> queryParams; + + /** + * The input SIA query parameters as structured by the ParamExtractor in cadcDALI. + * + * @param query query input parameters + * @see ca.nrc.cadc.dali.ParamExtractor + */ + public AdqlQueryGenerator(Map> query) { + this.queryParams = query; + } + + /** + * 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 ivoa.ObsCore WHERE dataproduct_type IN ( 'image', 'cube' )"); + + SiaParamValidator sia = new SiaParamValidator(); + List pos = sia.validatePOS(queryParams); + if (!pos.isEmpty()) { + boolean needOr = false; + if (pos.size() > 1) { + query.append(" AND ("); + } else { + query.append(" AND "); + } + 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(")"); + } + } + + List bands = sia.validateBAND(queryParams); + addNumericRangeConstraint(query, "em_min", "em_max", bands); + + List times = sia.validateTIME(queryParams); + addNumericRangeConstraint(query, "t_min", "t_max", times); + + List pols = sia.validatePOL(queryParams); + 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)); + //} + + if (pols.size() > 1) { + query.append(" AND ("); + } else { + query.append(" AND "); + } + 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 = sia.validateFOV(queryParams); + addNumericRangeConstraint(query, "s_fov", "s_fov", fovs); + + List ress = sia.validateSPATRES(queryParams); + addNumericRangeConstraint(query, "s_resolution", "s_resolution", ress); + + List exptimes = sia.validateEXPTIME(queryParams); + addNumericRangeConstraint(query, "t_exptime", "t_exptime", exptimes); + + List ids = sia.validateID(queryParams); + addStringListConstraint(query, "obs_publisher_did", ids); + + List collections = sia.validateCOLLECTION(queryParams); + addStringListConstraint(query, "obs_collection", collections); + + List facilities = sia.validateFACILITY(queryParams); + addStringListConstraint(query, "facility_name", facilities); + + List instruments = sia.validateINSTRUMENT(queryParams); + addStringListConstraint(query, "instrument_name", instruments); + + List dptypes = sia.validateDPTYPE(queryParams); + addStringListConstraint(query, "dataproduct_type", dptypes); + + List calibs = sia.validateCALIB(queryParams); + addIntegerListConstraint(query, "calib_level", calibs); + + List targets = sia.validateTARGET(queryParams); + addStringListConstraint(query, "target_name", targets); + + List timeress = sia.validateTIMERES(queryParams); + addNumericRangeConstraint(query, "t_resolution", "t_resolution", timeress); + + List specrps = sia.validateSPECRP(queryParams); + addNumericRangeConstraint(query, "em_res_power", "em_res_power", specrps); + + List formats = sia.validateFORMAT(queryParams); + addStringListConstraint(query, "access_format", formats); + + 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/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java b/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java new file mode 100644 index 00000000..3f6666cd --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java @@ -0,0 +1,111 @@ +/* +************************************************************************ +******************* 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.sia2; + +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.MemoryJobPersistence; +import ca.nrc.cadc.uws.server.RandomStringGenerator; +import ca.nrc.cadc.uws.server.SimpleJobManager; +import ca.nrc.cadc.uws.server.StringIDGenerator; +import ca.nrc.cadc.uws.server.SyncJobExecutor; +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(); + StringIDGenerator gen = new RandomStringGenerator(16); + IdentityManager im = AuthenticationUtil.getIdentityManager(); + // OK with execOnPOST in web.xml + JobPersistence jobPersist = new MemoryJobPersistence(gen, im, 60L); + + // exec jobs in request thread using custom SiaRunner + JobExecutor jobExec = new SyncJobExecutor(jobPersist, SiaRunner.class); + + super.setJobPersistence(jobPersist); + super.setJobExecutor(jobExec); + super.setMaxExecDuration(MAX_EXEC_DURATION); + super.setMaxDestruction(MAX_DESTRUCTION); + super.setMaxQuote(MAX_QUOTE); + } +} diff --git a/sia2/src/main/java/org/opencadc/sia2/ServiceAvailability.java b/sia2/src/main/java/org/opencadc/sia2/ServiceAvailability.java new file mode 100644 index 00000000..78d7b3ce --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/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.sia2; + +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()); + } + + SiaConfig conf = new SiaConfig(); + 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/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java b/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java new file mode 100644 index 00000000..f8f94a2b --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java @@ -0,0 +1,156 @@ +/* +************************************************************************ +******************* 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 : +* . +* +************************************************************************ +*/ + +package org.opencadc.sia2; + +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; +import org.opencadc.tap.TapClient; + +/** + * + * @author pdowler + */ +public class SiaConfig { + private static final Logger log = Logger.getLogger(SiaConfig.class); + + private static final String CONFIG = "sia2.properties"; + + private static final String BASE_KEY = "org.opencadc.sia2"; + private static final String QUERY_KEY = BASE_KEY + ".queryService"; + + private final URI queryService; + + public SiaConfig() { + 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; + } catch (InvalidConfigException ex) { + throw ex; + } + } + + 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/sia2/src/main/java/org/opencadc/sia2/SiaParamValidator.java b/sia2/src/main/java/org/opencadc/sia2/SiaParamValidator.java new file mode 100644 index 00000000..f41ecf72 --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/SiaParamValidator.java @@ -0,0 +1,170 @@ +/* +************************************************************************ +******************* 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.sia2; + +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 SiaParamValidator extends CommonParamValidator { + + private static final Logger log = Logger.getLogger(SiaParamValidator.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 ALLOWED_DPTYPES = Arrays.asList("cube", "image"); + + public SiaParamValidator() { + } + + 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) { + return validateString(DPTYPE, params, ALLOWED_DPTYPES); + } + + 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/sia2/src/main/java/org/opencadc/sia2/SiaRunner.java b/sia2/src/main/java/org/opencadc/sia2/SiaRunner.java new file mode 100644 index 00000000..01a5e6a0 --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/SiaRunner.java @@ -0,0 +1,229 @@ +/* +************************************************************************ +******************* 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.sia2; + +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 SIA-2.0 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 SiaRunner implements JobRunner { + + private static Logger log = Logger.getLogger(SiaRunner.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() { + log.debug("RUN SiaRunner: " + job.ownerSubject); + + 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(); + + ParamExtractor pe = new ParamExtractor(SiaParamValidator.QUERY_PARAMS); + Map> queryParams = pe.getParameters(job.getParameterList()); + + // Get the ADQL request parameters. + AdqlQueryGenerator queryGenerator = new AdqlQueryGenerator(queryParams); + Map parameters = queryGenerator.getParameterMap(); + parameters.put("FORMAT", 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; cadcUWS SyncServlet does + SiaConfig conf = new SiaConfig(); + 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/sia2/src/main/java/org/opencadc/sia2/UsageError.java b/sia2/src/main/java/org/opencadc/sia2/UsageError.java new file mode 100644 index 00000000..998ca3c9 --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/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.sia2; + +/** + * + * @author pdowler + */ +public class UsageError extends Throwable { + + public UsageError(String message) { + super(message); + } + +} diff --git a/sia2/src/main/webapp/META-INF/context.xml b/sia2/src/main/webapp/META-INF/context.xml new file mode 100644 index 00000000..701e7941 --- /dev/null +++ b/sia2/src/main/webapp/META-INF/context.xml @@ -0,0 +1,18 @@ + + + + WEB-INF/web.xml + + + + diff --git a/sia2/src/main/webapp/WEB-INF/web.xml b/sia2/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..f03a23f6 --- /dev/null +++ b/sia2/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,112 @@ + + + + + sia2 + + + index.html + + + + 1 + logControl + ca.nrc.cadc.log.LogControlServlet + + logLevel + info + + + logLevelPackages + + oprg.opencadc.sia2 + ca.nrc.cadc.uws + ca.nrc.cadc.rest + ca.nrc.cadc.sia + ca.nrc.cadc.sia2 + ca.nrc.cadc.vosi + + + + + + + 3 + SiaServlet + ca.nrc.cadc.uws.server.JobServlet + + 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.sia2.QueryJobManager + + + + + 3 + AvailabilityServlet + ca.nrc.cadc.vosi.AvailabilityServlet + + + + ca.nrc.cadc.vosi.AvailabilityPlugin + org.opencadc.sia2.ServiceAvailability + + + + + 2 + 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 + + + + + SiaServlet + /query/* + + + + logControl + /logControl/* + + + + AvailabilityServlet + /availability + + + CapabilitiesServlet + /capabilities + + + diff --git a/sia2/src/main/webapp/capabilities.xml b/sia2/src/main/webapp/capabilities.xml new file mode 100644 index 00000000..b2edf260 --- /dev/null +++ b/sia2/src/main/webapp/capabilities.xml @@ -0,0 +1,67 @@ + + + + + + https://replace.me.com/sia/capabilities + + + + + + https://replace.me.com/sia/availability + + + + + + https://replace.me.com/sia/logControl + + + + + + + + + https://replace.me.com/sia/query + + + + + + + collection + Specify the name of a single data collection to search: actual values that can + be used are dependent on the presence of calibrated images in the entire archive; + the values are given in the output as a field with name="collection". + The default is to search all collections (equivalent to collection="ALL"). + + string + + + mode + Specify the type of accessURL to include in the output. Recognised + values are archive (retrieve the full image) and cutout (retrieve approximately + the region-of-interest). The default mode is archive mode. + + string + + + + + + + + https://replace.me.com/sia/v2query + + + + + + + + diff --git a/sia2/src/main/webapp/index.html b/sia2/src/main/webapp/index.html new file mode 100644 index 00000000..88a1e77c --- /dev/null +++ b/sia2/src/main/webapp/index.html @@ -0,0 +1,165 @@ + + + + + SIA-2.0 API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+
+ + diff --git a/sia2/src/main/webapp/service.json b/sia2/src/main/webapp/service.json new file mode 100644 index 00000000..cd6643d9 --- /dev/null +++ b/sia2/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/sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java b/sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java new file mode 100644 index 00000000..ceb8ce5e --- /dev/null +++ b/sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java @@ -0,0 +1,287 @@ +/* +************************************************************************ +******************* 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.sia2; + +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("ca.nrc.cadc.sia2", Level.INFO); + } + + //@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); + 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); + 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); + 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); + 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); + 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); + } + } +} diff --git a/sia2/src/test/java/org/opencadc/sia2/SiaParamValidatorTest.java b/sia2/src/test/java/org/opencadc/sia2/SiaParamValidatorTest.java new file mode 100644 index 00000000..b269f217 --- /dev/null +++ b/sia2/src/test/java/org/opencadc/sia2/SiaParamValidatorTest.java @@ -0,0 +1,495 @@ +/* +************************************************************************ +******************* 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.sia2; + +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 SiaParamValidatorTest { + + private static final Logger log = Logger.getLogger(SiaParamValidatorTest.class); + + static { + Log4jInit.setLevel("ca.nrc.cadc.sia2", Level.INFO); + } + + SiaParamValidator sia = new SiaParamValidator(); + + @Test + public void testValidateFOV() { + + String[] testParams = new String[]{"FOV", "fov", "FoV"}; + + try { + List empty = sia.validateFOV(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = SiaParamValidator.class.getMethod("validateFOV", Map.class); + doValidateNumeric(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 = sia.validateSPATRES(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = SiaParamValidator.class.getMethod("validateSPATRES", Map.class); + doValidateNumeric(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 = sia.validateEXPTIME(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = SiaParamValidator.class.getMethod("validateEXPTIME", Map.class); + doValidateNumeric(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 = sia.validateCOLLECTION(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method method = SiaParamValidator.class.getMethod("validateCOLLECTION", Map.class); + doValidateString(method, 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 = sia.validateFACILITY(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method method = SiaParamValidator.class.getMethod("validateFACILITY", Map.class); + doValidateString(method, 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 = sia.validateINSTRUMENT(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method method = SiaParamValidator.class.getMethod("validateINSTRUMENT", Map.class); + doValidateString(method, testParams, null); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateDPTYPE() { + try { + String[] testParams = new String[]{"DPTYPE", "dptype", "DpTyPe"}; + String[] testValues = new String[]{"cube", "image"}; + + // null arg check + List empty = sia.validateDPTYPE(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method method = SiaParamValidator.class.getMethod("validateDPTYPE", Map.class); + doValidateString(method, testParams, testValues); + + // test invalid value + 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); + } + } 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 = sia.validateCALIB(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = SiaParamValidator.class.getMethod("validateCALIB", Map.class); + doValidateInteger(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 = sia.validateTARGET(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method method = SiaParamValidator.class.getMethod("validateTARGET", Map.class); + doValidateString(method, 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 = sia.validateTIMERES(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = SiaParamValidator.class.getMethod("validateTIMERES", Map.class); + doValidateNumeric(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 = sia.validateSPECRP(null); // compile and null arg check + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method m = SiaParamValidator.class.getMethod("validateSPECRP", Map.class); + doValidateNumeric(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 = sia.validateFORMAT(null); + Assert.assertNotNull(empty); + Assert.assertTrue(empty.isEmpty()); + + Method method = SiaParamValidator.class.getMethod("validateFORMAT", Map.class); + doValidateString(method, testParams, null); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testValidateCustomParam() { + try { + List empty = sia.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 = sia.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(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(sia, params); + Assert.assertNotNull(pols); + Assert.assertEquals(len[i], pols.size()); + } + } + } + + public void doValidateInteger(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(sia, 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(sia, 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(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(sia, 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(sia, 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); + } + } + +} From 6316f386e7a992ed24847f84596c0524a1d9b60b Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Fri, 29 Sep 2023 13:24:00 -0700 Subject: [PATCH 4/6] sia2: enable uws database init --- opencadc.gradle | 43 +++++++- .../org/opencadc/sia2/QueryJobManager.java | 8 +- .../java/org/opencadc/sia2/UWSInitAction.java | 99 +++++++++++++++++++ sia2/src/main/webapp/WEB-INF/web.xml | 10 +- sia2/src/main/webapp/capabilities.xml | 36 +------ 5 files changed, 154 insertions(+), 42 deletions(-) create mode 100644 sia2/src/main/java/org/opencadc/sia2/UWSInitAction.java 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", + } +} diff --git a/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java b/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java index 3f6666cd..3b5225ef 100644 --- a/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java +++ b/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java @@ -73,11 +73,9 @@ 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.MemoryJobPersistence; -import ca.nrc.cadc.uws.server.RandomStringGenerator; import ca.nrc.cadc.uws.server.SimpleJobManager; -import ca.nrc.cadc.uws.server.StringIDGenerator; import ca.nrc.cadc.uws.server.SyncJobExecutor; +import ca.nrc.cadc.uws.server.impl.PostgresJobPersistence; import org.apache.log4j.Logger; /** @@ -94,10 +92,8 @@ public class QueryJobManager extends SimpleJobManager { public QueryJobManager() { super(); - StringIDGenerator gen = new RandomStringGenerator(16); IdentityManager im = AuthenticationUtil.getIdentityManager(); - // OK with execOnPOST in web.xml - JobPersistence jobPersist = new MemoryJobPersistence(gen, im, 60L); + JobPersistence jobPersist = new PostgresJobPersistence(im); // exec jobs in request thread using custom SiaRunner JobExecutor jobExec = new SyncJobExecutor(jobPersist, SiaRunner.class); diff --git a/sia2/src/main/java/org/opencadc/sia2/UWSInitAction.java b/sia2/src/main/java/org/opencadc/sia2/UWSInitAction.java new file mode 100644 index 00000000..c34d2c20 --- /dev/null +++ b/sia2/src/main/java/org/opencadc/sia2/UWSInitAction.java @@ -0,0 +1,99 @@ +/* +************************************************************************ +******************* 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.sia2; + +import ca.nrc.cadc.db.DBUtil; +import ca.nrc.cadc.rest.InitAction; +import ca.nrc.cadc.uws.server.impl.InitDatabaseUWS; +import javax.sql.DataSource; +import org.apache.log4j.Logger; + +/** + * + * @author pdowler + */ +public class UWSInitAction extends InitAction { + private static final Logger log = Logger.getLogger(UWSInitAction.class); + + public UWSInitAction() { + } + + @Override + public void doInit() { + try { + DataSource uws = DBUtil.findJNDIDataSource("jdbc/uws"); + InitDatabaseUWS uwsi = new InitDatabaseUWS(uws, null, "uws"); + uwsi.doInit(); + log.info("init uws: OK"); + } catch (Exception ex) { + throw new RuntimeException("INIT FAIL", ex); + } + } + + +} diff --git a/sia2/src/main/webapp/WEB-INF/web.xml b/sia2/src/main/webapp/WEB-INF/web.xml index f03a23f6..3b819492 100644 --- a/sia2/src/main/webapp/WEB-INF/web.xml +++ b/sia2/src/main/webapp/WEB-INF/web.xml @@ -23,12 +23,12 @@ logLevelPackages - oprg.opencadc.sia2 + org.opencadc.sia2 ca.nrc.cadc.uws ca.nrc.cadc.rest - ca.nrc.cadc.sia - ca.nrc.cadc.sia2 ca.nrc.cadc.vosi + ca.nrc.cadc.auth + org.opencadc.auth @@ -38,6 +38,10 @@ 3 SiaServlet ca.nrc.cadc.uws.server.JobServlet + + init + org.opencadc.sia2.UWSInitAction + get ca.nrc.cadc.uws.web.SyncGetAction diff --git a/sia2/src/main/webapp/capabilities.xml b/sia2/src/main/webapp/capabilities.xml index b2edf260..b4a5c455 100644 --- a/sia2/src/main/webapp/capabilities.xml +++ b/sia2/src/main/webapp/capabilities.xml @@ -6,57 +6,29 @@ - https://replace.me.com/sia/capabilities + https://replace.me.com/sia2/capabilities - https://replace.me.com/sia/availability + https://replace.me.com/sia2/availability - https://replace.me.com/sia/logControl + https://replace.me.com/sia2/logControl - - - https://replace.me.com/sia/query - - - - - - - collection - Specify the name of a single data collection to search: actual values that can - be used are dependent on the presence of calibrated images in the entire archive; - the values are given in the output as a field with name="collection". - The default is to search all collections (equivalent to collection="ALL"). - - string - - - mode - Specify the type of accessURL to include in the output. Recognised - values are archive (retrieve the full image) and cutout (retrieve approximately - the region-of-interest). The default mode is archive mode. - - string - - - - - https://replace.me.com/sia/v2query + https://replace.me.com/sia2/query From 209010fe6d77743a982f677ce3732392e7b93c48 Mon Sep 17 00:00:00 2001 From: Christine Banek Date: Wed, 25 Oct 2023 15:41:22 -0700 Subject: [PATCH 5/6] Configurable ivoa.ObsCore table name Allows for configuration of the ivoa.ObsCore table name from the properties file. This allows us to configure multiple siav2 services to point at the same database / TAP service, each using a different table name. We have this case at Rubin where each butler dataset has its own ObsCore table. --- .../org/opencadc/sia2/AdqlQueryGenerator.java | 9 ++++-- .../java/org/opencadc/sia2/SiaConfig.java | 13 ++++++++ .../java/org/opencadc/sia2/SiaRunner.java | 4 +-- .../opencadc/sia2/AdqlQueryGeneratorTest.java | 31 ++++++++++++++++--- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java b/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java index 4323d668..1e0a8664 100644 --- a/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java +++ b/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java @@ -90,15 +90,18 @@ public class AdqlQueryGenerator { private static Logger log = Logger.getLogger(AdqlQueryGenerator.class); + private String tableName; private Map> queryParams; /** * The input SIA query parameters as structured by the ParamExtractor in cadcDALI. * * @param query query input parameters + * @param tableName ivoa.ObsCore table name * @see ca.nrc.cadc.dali.ParamExtractor */ - public AdqlQueryGenerator(Map> query) { + public AdqlQueryGenerator(Map> query, String tableName) { + this.tableName = tableName; this.queryParams = query; } @@ -118,7 +121,9 @@ public Map getParameterMap() { protected String getQuery() { StringBuilder query = new StringBuilder(); - query.append("SELECT * FROM ivoa.ObsCore WHERE dataproduct_type IN ( 'image', 'cube' )"); + query.append("SELECT * FROM "); + query.append(tableName); + query.append(" WHERE dataproduct_type IN ( 'image', 'cube' )"); SiaParamValidator sia = new SiaParamValidator(); List pos = sia.validatePOS(queryParams); diff --git a/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java b/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java index f8f94a2b..1837104f 100644 --- a/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java +++ b/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java @@ -92,8 +92,10 @@ public class SiaConfig { private static final String BASE_KEY = "org.opencadc.sia2"; private static final String QUERY_KEY = BASE_KEY + ".queryService"; + private static final String TABLE_KEY = BASE_KEY + ".table"; private final URI queryService; + private final String tableName; public SiaConfig() { StringBuilder sb = new StringBuilder(); @@ -119,11 +121,22 @@ public SiaConfig() { throw new InvalidConfigException("invalid config: " + sb.toString()); } this.queryService = qsURI; + + String tn = props.getFirstPropertyValue(TABLE_KEY); + if (tn == null) { + this.tableName = "ivoa.ObsCore"; + } else { + this.tableName = tn; + } } catch (InvalidConfigException ex) { throw ex; } } + public String getTableName() { + return tableName; + } + public URI getQueryService() { return queryService; } diff --git a/sia2/src/main/java/org/opencadc/sia2/SiaRunner.java b/sia2/src/main/java/org/opencadc/sia2/SiaRunner.java index 01a5e6a0..d5625d8a 100644 --- a/sia2/src/main/java/org/opencadc/sia2/SiaRunner.java +++ b/sia2/src/main/java/org/opencadc/sia2/SiaRunner.java @@ -162,11 +162,12 @@ private void doit() { mv.setMaxValue(MAX_MAXREC); Integer maxrec = mv.validate(); + SiaConfig conf = new SiaConfig(); ParamExtractor pe = new ParamExtractor(SiaParamValidator.QUERY_PARAMS); Map> queryParams = pe.getParameters(job.getParameterList()); // Get the ADQL request parameters. - AdqlQueryGenerator queryGenerator = new AdqlQueryGenerator(queryParams); + AdqlQueryGenerator queryGenerator = new AdqlQueryGenerator(queryParams, conf.getTableName()); Map parameters = queryGenerator.getParameterMap(); parameters.put("FORMAT", VOTableWriter.CONTENT_TYPE); if (maxrec != null) { @@ -175,7 +176,6 @@ private void doit() { // the implementation assumes that the /tap/sync service follows the // POST-redirect-GET (PrG) pattern; cadcUWS SyncServlet does - SiaConfig conf = new SiaConfig(); URL tapSyncURL = conf.getTapSyncURL(); // POST ADQL query to TAP but do not follow redirect to execute it. diff --git a/sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java b/sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java index ceb8ce5e..5c3ac582 100644 --- a/sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java +++ b/sia2/src/test/java/org/opencadc/sia2/AdqlQueryGeneratorTest.java @@ -108,7 +108,7 @@ public void testNoParams() { try { Map> params = new TreeMap>(new CaseInsensitiveStringComparator()); - AdqlQueryGenerator gen = new AdqlQueryGenerator(params); + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore"); Map tapParams = gen.getParameterMap(); String lang = (String) tapParams.get("LANG"); @@ -132,7 +132,7 @@ public void testScalarInterval() { params.put("BAND", Arrays.asList("550e-9")); params.put("TIME", Arrays.asList("54321.0")); - AdqlQueryGenerator gen = new AdqlQueryGenerator(params); + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore"); Map tapParams = gen.getParameterMap(); String lang = (String) tapParams.get("LANG"); @@ -176,7 +176,7 @@ public void testSingleParams() { params.put("SPECRP", Arrays.asList("-inf 500")); params.put("FORMAT", Arrays.asList("application/fits")); - AdqlQueryGenerator gen = new AdqlQueryGenerator(params); + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore"); String adql = gen.getQuery(); log.info("testSingleParams ADQL:\n" + adql); @@ -233,7 +233,7 @@ public void testMultipleParams() { params.put("SPECRP", Arrays.asList("-Inf 500", "200 300")); params.put("FORMAT", Arrays.asList("application/fits", "text/xml")); - AdqlQueryGenerator gen = new AdqlQueryGenerator(params); + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore"); String adql = gen.getQuery(); log.info("testMultipleParams ADQL:\n" + adql); @@ -273,7 +273,7 @@ public void testCoordRanges() { 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); + AdqlQueryGenerator gen = new AdqlQueryGenerator(params, "ivoa.ObsCore"); String adql = gen.getQuery(); log.info("testCoordRanges ADQL:\n" + adql); @@ -284,4 +284,25 @@ public void testCoordRanges() { Assert.fail("unexpected exception: " + unexpected); } } + + @Test + public void testAltTableNames() { + + 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, "schema.TableName"); + String adql = gen.getQuery(); + log.info("testCoordRanges ADQL:\n" + adql); + + String selectStmt = "SELECT * FROM schema.TableName WHERE"; + Assert.assertTrue("schema.TableName", adql.contains(selectStmt)); + 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); + } + } } From a6ed7044c511da4848a9dfbdc85c0a532c7e2986 Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Thu, 2 May 2024 09:55:30 -0700 Subject: [PATCH 6/6] cadc-datalink: update internal enum to match current vocab add static DataLink.Term.getTerm(String) utility method --- cadc-datalink/build.gradle | 2 +- .../java/org/opencadc/datalink/DataLink.java | 37 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/cadc-datalink/build.gradle b/cadc-datalink/build.gradle index 1086e075..016c4156 100644 --- a/cadc-datalink/build.gradle +++ b/cadc-datalink/build.gradle @@ -14,7 +14,7 @@ sourceCompatibility = '1.8' group = 'org.opencadc' -version = '1.1.1' +version = '1.1.2' description = 'OpenCADC DataLink library' def git_url = 'https://github.com/opencadc/dal' diff --git a/cadc-datalink/src/main/java/org/opencadc/datalink/DataLink.java b/cadc-datalink/src/main/java/org/opencadc/datalink/DataLink.java index ba66b05c..50365096 100644 --- a/cadc-datalink/src/main/java/org/opencadc/datalink/DataLink.java +++ b/cadc-datalink/src/main/java/org/opencadc/datalink/DataLink.java @@ -83,31 +83,31 @@ public class DataLink { * Terms from the http://www.ivoa.net/rdf/datalink/core vocabulary */ public enum Term { // TODO: re-use the VocabularyTerm code once extracted from caom2 - THIS("#this"), - PROGENITOR("#progenitor"), - DERIVATION("#derivation"), - DOCUMENTATION("#documentation"), AUXILIARY("#auxiliary"), - WEIGHT("#weight"), - ERROR("#error"), - NOISE("#noise"), - - CALIBRATION("#calibration"), BIAS("#bias"), + CALIBRATION("#calibration"), + CODERIVED("#coderived"), + COUNTERPART("#counterpart"), + CUTOUT("#cutout"), DARK("#dark"), + DERIVATION("#derivation"), + DETEACHED_HEADER("#detached-header"), + DOCUMENTATION("#documentation"), + ERROR("#error"), FLAT("#flat"), - + NOISE("#noise"), + PACKAGE("#package"), PREVIEW("#preview"), PREVIEW_IMAGE("#preview-image"), PREVIEW_PLOT("#preview-plot"), + PROC("#proc"), + PROGENITOR("#progenitor"), + THIS("#this"), THUMBNAIL("#thumbnail"), + WEIGHT("#weight"); - PROC("#proc"), - CUTOUT("#cutout"), - PACKAGE("#package"); - private final String value; private Term(String value) { @@ -117,6 +117,15 @@ private Term(String value) { public String getValue() { return value; } + + public static DataLink.Term getTerm(String s) { + for (Term t : values()) { + if (t.value.equals(s)) { + return t; + } + } + return null; + } } public enum LinkAuthTerm {