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..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
@@ -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,28 @@
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. 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
*/
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 +100,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 +108,7 @@ public String toString() {
}
public List getVertices() {
- return vertices;
+ return points;
}
@Override
@@ -104,12 +118,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 +131,344 @@ 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");
+ }
+ }
+
+ /**
+ * 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();
+ }
+ 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..bde8b8a8
--- /dev/null
+++ b/cadc-dali/src/test/java/ca/nrc/cadc/dali/PolygonTest.java
@@ -0,0 +1,243 @@
+/*
+************************************************************************
+******************* 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();
+
+ Assert.assertTrue("CCW", p.getCounterClockwise());
+
+ 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));
+
+ Assert.assertFalse("CCW", p.getCounterClockwise());
+
+ 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);
+ }
+ }
+}
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 {
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/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..1e0a8664
--- /dev/null
+++ b/sia2/src/main/java/org/opencadc/sia2/AdqlQueryGenerator.java
@@ -0,0 +1,359 @@
+/*
+************************************************************************
+******************* 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 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, String tableName) {
+ this.tableName = tableName;
+ 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 ");
+ query.append(tableName);
+ query.append(" 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..3b5225ef
--- /dev/null
+++ b/sia2/src/main/java/org/opencadc/sia2/QueryJobManager.java
@@ -0,0 +1,107 @@
+/*
+************************************************************************
+******************* CANADIAN ASTRONOMY DATA CENTRE *******************
+************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES **************
+*
+* (c) 2023. (c) 2023.
+* Government of Canada Gouvernement du Canada
+* National Research Council Conseil national de recherches
+* Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6
+* All rights reserved Tous droits réservés
+*
+* NRC disclaims any warranties, Le CNRC dénie toute garantie
+* expressed, implied, or énoncée, implicite ou légale,
+* statutory, of any kind with de quelque nature que ce
+* respect to the software, soit, concernant le logiciel,
+* including without limitation y compris sans restriction
+* any warranty of merchantability toute garantie de valeur
+* or fitness for a particular marchande ou de pertinence
+* purpose. NRC shall not be pour un usage particulier.
+* liable in any event for any Le CNRC ne pourra en aucun cas
+* damages, whether direct or être tenu responsable de tout
+* indirect, special or general, dommage, direct ou indirect,
+* consequential or incidental, particulier ou général,
+* arising from the use of the accessoire ou fortuit, résultant
+* software. Neither the name de l'utilisation du logiciel. Ni
+* of the National Research le nom du Conseil National de
+* Council of Canada nor the Recherches du Canada ni les noms
+* names of its contributors may de ses participants ne peuvent
+* be used to endorse or promote être utilisés pour approuver ou
+* products derived from this promouvoir les produits dérivés
+* software without specific prior de ce logiciel sans autorisation
+* written permission. préalable et particulière
+* par écrit.
+*
+* This file is part of the Ce fichier fait partie du projet
+* OpenCADC project. OpenCADC.
+*
+* OpenCADC is free software: OpenCADC est un logiciel libre ;
+* you can redistribute it and/or vous pouvez le redistribuer ou le
+* modify it under the terms of modifier suivant les termes de
+* the GNU Affero General Public la “GNU Affero General Public
+* License as published by the License” telle que publiée
+* Free Software Foundation, par la Free Software Foundation
+* either version 3 of the : soit la version 3 de cette
+* License, or (at your option) licence, soit (à votre gré)
+* any later version. toute version ultérieure.
+*
+* OpenCADC is distributed in the OpenCADC est distribué
+* hope that it will be useful, dans l’espoir qu’il vous
+* but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE
+* without even the implied GARANTIE : sans même la garantie
+* warranty of MERCHANTABILITY implicite de COMMERCIALISABILITÉ
+* or FITNESS FOR A PARTICULAR ni d’ADÉQUATION À UN OBJECTIF
+* PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence
+* General Public License for Générale Publique GNU Affero
+* more details. pour plus de détails.
+*
+* You should have received Vous devriez avoir reçu une
+* a copy of the GNU Affero copie de la Licence Générale
+* General Public License along Publique GNU Affero avec
+* with OpenCADC. If not, see OpenCADC ; si ce n’est
+* . pas le cas, consultez :
+* .
+*
+* $Revision: 5 $
+*
+************************************************************************
+ */
+
+package org.opencadc.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.SimpleJobManager;
+import ca.nrc.cadc.uws.server.SyncJobExecutor;
+import ca.nrc.cadc.uws.server.impl.PostgresJobPersistence;
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * @author pdowler
+ */
+public class QueryJobManager extends SimpleJobManager {
+
+ private static final Logger log = Logger.getLogger(QueryJobManager.class);
+
+ private static final Long MAX_EXEC_DURATION = 600L;
+ private static final Long MAX_DESTRUCTION = 7 * 24 * 3600L; // 1 week
+ private static final Long MAX_QUOTE = 600L; // same as exec since we don't queue
+
+ public QueryJobManager() {
+ super();
+ IdentityManager im = AuthenticationUtil.getIdentityManager();
+ JobPersistence jobPersist = new PostgresJobPersistence(im);
+
+ // exec jobs in request thread using custom SiaRunner
+ JobExecutor jobExec = new SyncJobExecutor(jobPersist, 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..1837104f
--- /dev/null
+++ b/sia2/src/main/java/org/opencadc/sia2/SiaConfig.java
@@ -0,0 +1,169 @@
+/*
+************************************************************************
+******************* 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 static final String TABLE_KEY = BASE_KEY + ".table";
+
+ private final URI queryService;
+ private final String tableName;
+
+ 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;
+
+ 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;
+ }
+
+ 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..d5625d8a
--- /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();
+
+ 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, conf.getTableName());
+ 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
+ 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/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/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..3b819492
--- /dev/null
+++ b/sia2/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,116 @@
+
+
+
+
+ sia2
+
+
+ index.html
+
+
+
+ 1
+ logControl
+ ca.nrc.cadc.log.LogControlServlet
+
+ logLevel
+ info
+
+
+ logLevelPackages
+
+ org.opencadc.sia2
+ ca.nrc.cadc.uws
+ ca.nrc.cadc.rest
+ ca.nrc.cadc.vosi
+ ca.nrc.cadc.auth
+ org.opencadc.auth
+
+
+
+
+
+
+ 3
+ SiaServlet
+ ca.nrc.cadc.uws.server.JobServlet
+
+ init
+ org.opencadc.sia2.UWSInitAction
+
+
+ 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..b4a5c455
--- /dev/null
+++ b/sia2/src/main/webapp/capabilities.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+ https://replace.me.com/sia2/capabilities
+
+
+
+
+
+ https://replace.me.com/sia2/availability
+
+
+
+
+
+ https://replace.me.com/sia2/logControl
+
+
+
+
+
+
+
+
+
+ https://replace.me.com/sia2/query
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+