From 6f154e19c59ff751bb5880dc9a11c0ca13968a9e Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Thu, 1 Sep 2016 15:06:13 +0200
Subject: [PATCH] [TAP] Let specify a mapping for TAP_SCHEMA between ADQL and
 DB names. This is very helpful if TAP_SCHEMA (or some of its tables and their
 columns) has a different name in the database. The mapping can be specified
 to JDBCConnection or directly in the configuration file.

---
 .../config/ConfigurableServiceConnection.java | 45 +++++++---
 src/tap/config/tap_configuration_file.html    | 27 +++++-
 src/tap/config/tap_full.properties            | 23 ++++-
 src/tap/db/JDBCConnection.java                | 86 ++++++++++++++++++-
 4 files changed, 161 insertions(+), 20 deletions(-)

diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java
index 341f577..fff3625 100644
--- a/src/tap/config/ConfigurableServiceConnection.java
+++ b/src/tap/config/ConfigurableServiceConnection.java
@@ -86,13 +86,19 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Properties;
 
+import adql.db.FunctionDef;
+import adql.db.STCS;
+import adql.parser.ParseException;
+import adql.query.operand.function.UserDefinedFunction;
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPFactory;
 import tap.db.DBConnection;
+import tap.db.JDBCConnection;
 import tap.formatter.FITSFormat;
 import tap.formatter.HTMLFormat;
 import tap.formatter.JSONFormat;
@@ -111,10 +117,6 @@ import uws.service.UserIdentifier;
 import uws.service.file.LocalUWSFileManager;
 import uws.service.file.UWSFileManager;
 import uws.service.log.UWSLog.LogLevel;
-import adql.db.FunctionDef;
-import adql.db.STCS;
-import adql.parser.ParseException;
-import adql.query.operand.function.UserDefinedFunction;
 
 /**
  * <p>Concrete implementation of {@link ServiceConnection}, fully parameterized with a TAP configuration file.</p>
@@ -125,7 +127,7 @@ import adql.query.operand.function.UserDefinedFunction;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.1 (02/2016)
+ * @version 2.1 (09/2016)
  * @since 2.0
  */
 public final class ConfigurableServiceConnection implements ServiceConnection {
@@ -329,7 +331,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	 * <p>
 	 * 	If not an absolute URI, the given path may be either relative or absolute. A relative path is always considered
 	 * 	as relative from the Web Application directory (supposed to be given in 2nd parameter).
-	 * </p> 
+	 * </p>
 	 * 
 	 * @param filePath			URI/Path/Name of the file to get.
 	 * @param webAppRootPath	Web Application directory local path.
@@ -482,7 +484,22 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		else if (metaFetchType.equalsIgnoreCase(VALUE_DB)){
 			DBConnection conn = null;
 			try{
+				// get a db connection:
 				conn = tapFactory.getConnection("GET_TAP_SCHEMA");
+
+				// fetch and set the ADQL<->DB mapping for all standard TAP_SCHEMA items:
+				if (conn instanceof JDBCConnection){
+					HashMap<String,String> dbMapping = new HashMap<String,String>(10);
+					// fetch the mapping from the Property file:
+					for(String key : tapConfig.stringPropertyNames()){
+						if (key.trim().startsWith("TAP_SCHEMA") && tapConfig.getProperty(key) != null && tapConfig.getProperty(key).trim().length() > 0)
+							dbMapping.put(key.trim(), tapConfig.getProperty(key));
+					}
+					// set the mapping into the DB connection:
+					((JDBCConnection)conn).setDBMapping(dbMapping);
+				}
+
+				// fetch TAP_SCHEMA:
 				metadata = conn.getTAPSchema();
 			}finally{
 				if (conn != null)
@@ -970,7 +987,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 			Object[] limit = parseLimit(propValue, KEY_UPLOAD_MAX_FILE_SIZE, true);
 			if (((Integer)limit[0]).intValue() <= 0)
 				limit[0] = new Integer(TAPConfiguration.DEFAULT_UPLOAD_MAX_FILE_SIZE);
-			// ...check that the unit is correct (bytes): 
+			// ...check that the unit is correct (bytes):
 			if (!LimitUnit.bytes.isCompatibleWith((LimitUnit)limit[1]))
 				throw new TAPException("The maximum upload file size " + KEY_UPLOAD_MAX_FILE_SIZE + " (here: " + propValue + ") can not be expressed in a unit different from bytes (B, kB, MB, GB)!");
 			// ...set the max file size:
@@ -1135,10 +1152,12 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 			char c;
 			int ind = 0;
 			short nbComma = 0;
-			boolean within_item = false, within_params = false, within_classpath = false;
+			boolean within_item = false, within_params = false,
+					within_classpath = false;
 			StringBuffer buf = new StringBuffer();
 			String signature, classpath;
-			int[] posSignature = new int[]{-1,-1}, posClassPath = new int[]{-1,-1};
+			int[] posSignature = new int[]{-1,-1},
+					posClassPath = new int[]{-1,-1};
 
 			signature = null;
 			classpath = null;
@@ -1307,7 +1326,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	 * 	In other words, if the given value is less or equals to the current maximum retention period.
 	 * </em></p>
 	 * 
-	 * @param period	New default retention period (in seconds). 
+	 * @param period	New default retention period (in seconds).
 	 * 
 	 * @return	<i>true</i> if the given retention period has been successfully set, <i>false</i> otherwise.
 	 */
@@ -1355,7 +1374,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	 * 	In other words, if the given value is less or equals to the current maximum execution duration.
 	 * </em></p>
 	 * 
-	 * @param duration	New default execution duration (in milliseconds). 
+	 * @param duration	New default execution duration (in milliseconds).
 	 * 
 	 * @return	<i>true</i> if the given execution duration has been successfully set, <i>false</i> otherwise.
 	 */
@@ -1451,7 +1470,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	 * 	In other words, if the given value is less or equals to the current maximum output limit.
 	 * </em></p>
 	 * 
-	 * @param limit	New default output limit (in number of rows). 
+	 * @param limit	New default output limit (in number of rows).
 	 * 
 	 * @return	<i>true</i> if the given output limit has been successfully set, <i>false</i> otherwise.
 	 */
@@ -1546,7 +1565,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	 * 	In other words, if the given value is less or equals to the current maximum upload limit.
 	 * </em></p>
 	 * 
-	 * @param limit	New default upload limit. 
+	 * @param limit	New default upload limit.
 	 * 
 	 * @return	<i>true</i> if the given upload limit has been successfully set, <i>false</i> otherwise.
 	 */
diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html
index 5edc7fa..df438c9 100644
--- a/src/tap/config/tap_configuration_file.html
+++ b/src/tap/config/tap_configuration_file.html
@@ -302,6 +302,31 @@
 				</td>
 				<td><ul><li>/home/foo/my_metadata.xml</li><li>my_metadata.xml</li><li>WEB-INF/my_metadata.xml</li></ul></td>
 			</tr>
+			<tr class="optional">
+				<td class="done">TAP_SCHEMA...</td>
+				<td></td>
+				<td>text</td>
+				<td>
+					<p><strong>Only used if <code>metadata = db</code></strong>.</p>
+					<p>Mapping between TAP_SCHEMA ADQL names and their names in the database.</p>
+					<p>
+						<b>Any property named exactly (case sensitive) like TAP_SCHEMA items</b> will be considered
+						as a mapping between its ADQL name and its DB name.
+					</p>
+					<p>
+						The property value MUST be NOT qualified. Just the item name is required.
+						The value will be used as provided (with the same case).
+					</p>
+				</td>
+				<td>
+					<ul>
+						<li>TAP_SCHEMA = myTAPSchema</li>
+						<li>TAP_SCHEMA.tables = tap_tables</li>
+						<li>TAP_SCHEMA.columns.column_name = name</li>
+						<li>...</li>
+					</ul>
+				</td>
+			</tr>
 			
 			<tr><td colspan="5">Files</td></tr>
 			<tr class="mandatory">
@@ -758,7 +783,7 @@
 						tap.AbstractTAPFactory or tap.config.ConfigurableTAPFactory.
 					</p>
 					<p><em>By default, the default TAPFactory (tap.config.ConfigurableTAPFactory) is used and may use all properties related to the backup management,
-					the database access and the ADQL translation.</em></p>
+					the database access, the TAP_SCHEMA mapping and the ADQL translation.</em></p>
 				</td>
 				<td>{aPackage.MyTAPFactory}</td>
 			</tr>
diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties
index 770f78e..5bbedbf 100644
--- a/src/tap/config/tap_full.properties
+++ b/src/tap/config/tap_full.properties
@@ -2,7 +2,7 @@
 #             FULL TAP CONFIGURATION FILE                #
 #                                                        #
 # TAP Version: 2.1                                       #
-# Date: 18 July 2016                                     #
+# Date: 1 Sept. 2016                                     #
 # Author: Gregory Mantelet (ARI)                         #
 #                                                        #
 ########################################################## 
@@ -158,14 +158,29 @@ db_password =
 # one constructor with the following parameters: (TAPMetadata) or (TAPMetadata, UWSFileManager, TAPFactory, TAPLog).
 #  
 # Allowed values: xml, xml {myTAPMetadata}, db, db {myTAPMetadata} or a full class name (between {}).
-metadata =  
+metadata = 
 
 # [MANDATORY]
 # Mandatory if the value of "metadata" is "xml".
 # Local file path to the TableSet XML document.
 # The XML document must implement the schema TableSet defined by VODataService.
 # The file path must be either an absolute local file path or a file path relative to WebContent (i.e. the web application directory in which there are WEB-INF and META-INF).
-metadata_file = 
+metadata_file =   
+
+# [OPTIONAL]
+# [ONLY USED IF metadata = db]
+#
+# Mapping between TAP_SCHEMA ADQL names and their names in the database.
+# 
+# Any property named exactly (case sensitive) like TAP_SCHEMA items will be considered
+# as a mapping between its ADQL name and its DB name.
+#
+# Examples: "TAP_SCHEMA = TAP_SCHEMA2" or "TAP_SCHEMA.columns.column_name = name"
+#
+# The property value MUST be NOT qualified. Just the item name is required.
+# The value will be used as provided (with the same case).
+#
+# TAP_SCHEMA = 
 
 #########
 # FILES #
@@ -568,5 +583,5 @@ additional_resources =
 # tap.AbstractTAPFactory or tap.config.ConfigurableTAPFactory.
 # 
 # By default, the default TAPFactory (tap.config.ConfigurableTAPFactory) is used and may use all properties related to the backup management,
-# the database access and the ADQL translation.
+# the database access, the TAP_SCHEMA mapping and the ADQL translation.
 tap_factory = 
diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java
index 15cda97..d65886f 100644
--- a/src/tap/db/JDBCConnection.java
+++ b/src/tap/db/JDBCConnection.java
@@ -177,7 +177,7 @@ import uws.service.log.UWSLog.LogLevel;
  * </i></p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.1 (07/2016)
+ * @version 2.1 (09/2016)
  * @since 2.0
  */
 public class JDBCConnection implements DBConnection {
@@ -302,6 +302,17 @@ public class JDBCConnection implements DBConnection {
 	 * <p><i>Note 2: if this feature is enabled (i.e. has a value &gt; 0), the AutoCommit will be disabled.</i></p> */
 	protected int fetchSize = DEFAULT_FETCH_SIZE;
 
+	/* TAP_SCHEMA MAPPING */
+
+	/** Mapping of the TAP_SCHEMA items between their ADQL name and their name in the database.
+	 * <p><b>IMPORTANT:</b>
+	 * 	Keys of the map MUST be the full ADQL name of an item (e.g. TAP_SCHEMA, TAP_SCHEMA.tables, TAP_SCHEMA.columns.ucd).
+	 * 	Values MUST be the name of the corresponding item in the database.
+	 * 	Keys and values are case sensitive.
+	 * </p>
+	 * @since 2.1 */
+	protected Map<String,String> dbMapping = null;
+
 	/**
 	 * <p>Creates a JDBC connection to the specified database and with the specified JDBC driver.
 	 * This connection is established using the given user name and password.<p>
@@ -792,6 +803,77 @@ public class JDBCConnection implements DBConnection {
 		}
 	}
 
+	/**
+	 * Let specify for all item of the standard TAP_SCHEMA a different name in the database.
+	 * <p><i>
+	 * 	For instance: if in the database "TAP_SCHEMA" is called "MY_TAP_SCHEMA".
+	 * </i></p>
+	 * 
+	 * <p><b>IMPORTANT:</b>
+	 * 	TAP_SCHEMA items (i.e. keys in the map) MUST be fully qualified ADQL names (e.g. TAP_SCHEMA.columns.name).
+	 * 	The values MUST be single database names (i.e. no catalogue, schema or table prefix).
+	 * 	Both keys and values are case sensitive.
+	 * </p>
+	 * 
+	 * <p><i>Note:</i>
+	 * 	TAP_SCHEMA items keeping the same name in the database than in ADQL do not need to
+	 * 	be listed in the given map.
+	 * </p>
+	 * 
+	 * @param mapping	Mapping between ADQL names and DB names.
+	 *               	If <code>null</code>, DB names will be considered equals to the ADQL names.
+	 * 
+	 * @since 2.1
+	 */
+	public void setDBMapping(final Map<String,String> mapping){
+		if (mapping == null)
+			dbMapping = null;
+		else{
+			if (dbMapping == null)
+				dbMapping = new HashMap<String,String>(mapping.size());
+			else
+				dbMapping.clear();
+			dbMapping.putAll(mapping);
+			if (dbMapping.size() == 0)
+				dbMapping = null;
+		}
+	}
+
+	/**
+	 * Get the standard definition of TAP_SCHEMA with eventually DB names provided by the set mapping (see {@link #setDBMapping(Map)}).
+	 * 
+	 * @return	The standard schema as it should be detected in the database.
+	 * 
+	 * @since 2.1
+	 */
+	protected TAPSchema getStdSchema(){
+		TAPSchema tap_schema = TAPMetadata.getStdSchema(supportsSchema);
+
+		if (dbMapping != null){
+			// Update the TAP_SCHEMA DB name, if needed:
+			if (dbMapping.containsKey(tap_schema.getADQLName()))
+				tap_schema.setDBName(dbMapping.get(tap_schema.getADQLName()));
+
+			// For each table...
+			for(TAPTable t : tap_schema){
+				// ...update the table DB name, if needed:
+				if (dbMapping.containsKey(t.getFullName()))
+					t.setDBName(dbMapping.get(t.getFullName()));
+
+				// For each column...
+				String fullName;
+				for(DBColumn c : t){
+					fullName = t.getFullName() + "." + c.getADQLName();
+					// ...update the column DB name, if needed:
+					if (dbMapping.containsKey(fullName))
+						((TAPColumn)c).setDBName(dbMapping.get(fullName));
+				}
+			}
+		}
+
+		return tap_schema;
+	}
+
 	/* ************************************ */
 	/* GETTING TAP_SCHEMA FROM THE DATABASE */
 	/* ************************************ */
@@ -822,7 +904,7 @@ public class JDBCConnection implements DBConnection {
 		TAPMetadata metadata = new TAPMetadata();
 
 		// Get the definition of the standard TAP_SCHEMA tables:
-		TAPSchema tap_schema = TAPMetadata.getStdSchema(supportsSchema);
+		TAPSchema tap_schema = getStdSchema();
 
 		// LOAD ALL METADATA FROM THE STANDARD TAP TABLES:
 		try{
-- 
GitLab