From 8102b083b762d1d399de46333c6ebfd97d538c0b Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Tue, 26 Sep 2017 17:00:57 +0200
Subject: [PATCH] [TAP] Auto-detect the RegTAP datamodel from the schemas and
 tables declared in TAP_SCHEMA.

RegTAP is detected successfully if the schema 'rr' exists (case sensitive)
and contains at least the following tables (names also case sensitive):
	- capability
	- interface
	- intf_param
	- relationship
	- res_date
	- res_detail
	- res_role
	- res_schema
	- res_subject
	- res_table
	- resource
	- table_column
	- validation

The table name can be prefixed by 'rr' (case sensitive) or not. For instance:
'rr.capability' and 'capability' are both detected successfully.

All these constraints (including the case sensitive one) are based on the
requirements of the RegTAP-1.0 standard document. They are set in order
to not declare the RegTAP DM by accident AND to provide a first low
validation of the RegTAP schema and tables. Low validation because
columns (as well as datatype, utypes, indices and UDF functions) are
never checked.

This commit resolves the GitHub issue #51
---
 src/tap/metadata/TAPMetadata.java | 76 +++++++++++++++++++++++++------
 src/tap/resource/TAP.java         | 46 +++++++++++++++++--
 2 files changed, 105 insertions(+), 17 deletions(-)

diff --git a/src/tap/metadata/TAPMetadata.java b/src/tap/metadata/TAPMetadata.java
index 01b5d90..dfb9a7f 100644
--- a/src/tap/metadata/TAPMetadata.java
+++ b/src/tap/metadata/TAPMetadata.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -38,7 +39,6 @@ import javax.servlet.http.HttpServletResponse;
 import adql.db.DBTable;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
-import tap.db.JDBCConnection;
 import tap.metadata.TAPTable.TableType;
 import tap.resource.Capabilities;
 import tap.resource.TAPResource;
@@ -52,18 +52,27 @@ import uws.UWSToolBox;
  * This list also corresponds to the TAP resource "/tables".</p>
  * 
  * <p>
- * 	Only schemas are stored in this object. So that's why only schemas can be added and removed
- * 	from this class. However, {@link TAPSchema} objects are listing tables, whose the object
- * 	representation is listing columns. So to add tables, you must first embed them in a schema.
+ * 	Only schemas are stored in this object. So that's why only schemas can be
+ * 	added and removed from this class. However, {@link TAPSchema} objects are
+ * 	listing tables, whose the object representation is listing columns. So to
+ * 	add tables, you must first embed them in a schema.
  * </p>
  * 
  * <p>
- * 	All metadata have two names: one to use in ADQL queries and the other to use when really querying
- * 	the database. This is very useful to hide the real complexity of the database and propose
- * 	a simpler view of the query-able data. It is particularly useful if a schema does not exist in the
- * 	database but has been added in the TAP schema for more logical separation on the user point of view.
- * 	In a such case, the schema would have an ADQL name but no DB name (NULL value ; which is possible only
- * 	with {@link TAPSchema} objects).
+ * 	All metadata have two names: one to use in ADQL queries and the other to use
+ * 	when really querying the database. This is very useful to hide the real
+ * 	complexity of the database and propose a simpler view of the query-able
+ * 	data. It is particularly useful if a schema does not exist in the database
+ * 	but has been added in the TAP schema for more logical separation on the user
+ * 	point of view. In a such case, the schema would have an ADQL name but no DB
+ * 	name (NULL value ; which is possible only with {@link TAPSchema} objects).
+ * </p>
+ * 
+ * <p>
+ * 	This class lets also detect the ObsCore and RegTAP data models, thanks to
+ * 	the functions {@link #getObsCoreTable()} and {@link #getRegTAPSchema()}
+ * 	which return resp. the ObsCore table and the RegTAP schema if matching the
+ * 	IVOA specification.
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
@@ -401,11 +410,12 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * 	This function is case sensitive only on the schema name
 	 * 	(i.e. <code>ivoa</code>) which must be defined in full lower case.
 	 * 	The table name (i.e. <code>ObsCore</code>) will be found whatever
-	 * 	the case it is written in.
+	 * 	the case it is written in and whether it is prefixed or not.
 	 * </p>
 	 * 
 	 * @return	Description of the ObsCore table,
-	 *        	or <code>NULL</code> if this table is not provided by this TAP service.
+	 *        	or <code>NULL</code> if this table is not provided by this TAP
+	 *        	service.
 	 * 
 	 * @since 2.1
 	 */
@@ -413,13 +423,51 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 		TAPSchema ivoaSchema = getSchema("ivoa");
 		if (ivoaSchema != null){
 			for(TAPTable t : ivoaSchema){
-				if (t.getADQLName().equalsIgnoreCase("obscore"))
+				if (t.getADQLName().matches("(?i)(ivoa\\.)?obscore"))
 					return t;
 			}
 		}
 		return null;
 	}
 
+	/**
+	 * Get the description of the RegTAP schema, if it is defined.
+	 * 
+	 * <p>
+	 * 	This function is case sensitive only on the schema name
+	 * 	(i.e. <code>rr</code>) which must be defined in full lower case.
+	 * 	The tables name (e.g. <code>capability</code>) will be searched case
+	 * 	sensitively as well. However, it does not matter if the table name is
+	 * 	prefixed or not.
+	 * </p>
+	 * 
+	 * @return	Description of the RegTAP schema,
+	 *        	or <code>NULL</code> if RegTAP is not supported in this TAP
+	 *        	service.
+	 * 
+	 * @since 2.1
+	 */
+	public TAPSchema getRegTAPSchema(){
+		// Get the RegTAP schema if existing:
+		TAPSchema rrSchema = getSchema("rr");
+
+		// If there is one, check its content:
+		if (rrSchema != null){
+			// search for all mandatory tables of RegTAP:
+			HashSet<String> matchedTables = new HashSet<String>(13);
+			for(TAPTable t : rrSchema){
+				if (t.getADQLName().matches("(rr\\.)?(capability|interface|intf_param|relationship|res_date|res_detail|res_role|res_schema|res_subject|res_table|resource|table_column|validation)"))
+					matchedTables.add(t.getADQLName());
+			}
+			// if all columns have been found, return the RegTAP schema:
+			if (matchedTables.size() == 13)
+				return rrSchema;
+		}
+
+		// NULL, if no schema rr or if it does match the IVOA specification:
+		return null;
+	}
+
 	/**
 	 * Get the number of all tables contained in this TAP metadata set.
 	 * 
@@ -895,7 +943,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 
 	/**
 	 * Get the minimum definition of the table TAP_SCHEMA.coosys as expected by
-	 * the library (see {@link JDBCConnection#getTAPSchema()}.
+	 * the library (see {@link tap.db.JDBCConnection#getTAPSchema()}.
 	 * 
 	 * @return	The created definition of TAP_SCHEMA.coosys.
 	 * 
diff --git a/src/tap/resource/TAP.java b/src/tap/resource/TAP.java
index e8fa139..e4ab144 100644
--- a/src/tap/resource/TAP.java
+++ b/src/tap/resource/TAP.java
@@ -39,6 +39,7 @@ import tap.error.DefaultTAPErrorWriter;
 import tap.formatter.OutputFormat;
 import tap.log.TAPLog;
 import tap.metadata.TAPMetadata;
+import tap.metadata.TAPSchema;
 import tap.metadata.TAPTable;
 import uk.ac.starlink.votable.VOSerializer;
 import uws.UWSException;
@@ -55,7 +56,7 @@ import uws.service.log.UWSLog.LogLevel;
  * <p>At its creation it is creating and configuring the other resources in function of the given description of the TAP service.</p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.1 (03/2017)
+ * @version 2.1 (09/2017)
  */
 public class TAP implements VOSIResource {
 
@@ -634,6 +635,7 @@ public class TAP implements VOSIResource {
 	 * <p>Currently, only the following DMs are natively supported:</p>
 	 * <ul>
 	 * 	<li>Obscore (1.0 and PR-1.1)</li>
+	 * 	<li>RegTAP (1.0)</li>
 	 * </ul>
 	 * 
 	 * <p>
@@ -653,7 +655,10 @@ public class TAP implements VOSIResource {
 	 * @since 2.1
 	 */
 	protected void appendDataModels(final StringBuffer xml, final String linePrefix){
+		// ObsCore:
 		appendObsCoreDM(xml, linePrefix);
+		// RegTAP:
+		appendRegTAPDM(xml, linePrefix);
 	}
 
 	/**
@@ -676,8 +681,8 @@ public class TAP implements VOSIResource {
 	 * 	If not, the Obscore table will be declared as Obscore 1.0.
 	 * </p>
 	 * 
-	 * @param xml	The <code>/capabilities</code> in-progress content in which
-	 *           	Obscore-DM should be declared if found.
+	 * @param xml			The <code>/capabilities</code> in-progress content
+	 *           			in which Obscore-DM should be declared if found.
 	 * @param linePrefix	Tabulations/Spaces that should prefix all lines
 	 *                  	(for human readability).
 	 * 
@@ -714,6 +719,41 @@ public class TAP implements VOSIResource {
 		}
 	}
 
+	/**
+	 * <p>Append the RegTAP DM declaration in the given {@link StringBuffer}
+	 * if a schema <code>rr</code> can be found in <code>TAP_SCHEMA</code>
+	 * with all its required tables.</p>
+	 * 
+	 * <p>
+	 * 	This function has no effect if the schema <code>rr</code> or its
+	 * 	mandatory children tables can not be found. The research is done
+	 * 	case sensitively by {@link TAPMetadata#getRegTAPSchema()}.
+	 * </p>
+	 * 
+	 * <p>
+	 * 	If there is a valid schema <code>rr</code>, this function
+	 * 	detects automatically which version of RegTAP is implemented. For the
+	 * 	moment only one is supported: RegTAP-1.0.
+	 * </p>
+	 * 
+	 * @param xml			The <code>/capabilities</code> in-progress content
+	 *           			in which RegTAP-DM should be declared if found.
+	 * @param linePrefix	Tabulations/Spaces that should prefix all lines
+	 *                  	(for human readability).
+	 * 
+	 * @see TAPMetadata#getRegTAPTable()
+	 * 
+	 * @since 2.1
+	 */
+	protected void appendRegTAPDM(final StringBuffer xml, final String linePrefix){
+		// Try to get the RegTAP schema definition:
+		TAPSchema regtap = service.getTAPMetadata().getRegTAPSchema();
+
+		// If there is one, determine the supported DM version and declare it:
+		if (regtap != null)
+			xml.append(linePrefix + "<dataModel ivo-id=\"ivo://ivoa.net/std/RegTAP#1.0\">Registry 1.0</dataModel>\n");
+	}
+
 	/* ************************************* */
 	/* MANAGEMENT OF THIS RESOURCE'S CONTENT */
 	/* ************************************* */
-- 
GitLab