From 62a44426fd07a50e96e5c5f1d19e2cf97a878913 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Thu, 19 Feb 2015 12:04:01 +0100
Subject: [PATCH] [TAP] Add a property in the configuration file to set a
 custom TAPFactory (in replacement of ConfigurableTAPFactory.

---
 .../config/ConfigurableServiceConnection.java | 13 ++-
 src/tap/config/TAPConfiguration.java          |  3 +
 src/tap/config/tap_configuration_file.html    | 21 ++++-
 src/tap/config/tap_full.properties            | 18 +++-
 .../TestConfigurableServiceConnection.java    | 90 ++++++++++++++++++-
 5 files changed, 138 insertions(+), 7 deletions(-)

diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java
index 0d4add6..ab4be58 100644
--- a/src/tap/config/ConfigurableServiceConnection.java
+++ b/src/tap/config/ConfigurableServiceConnection.java
@@ -27,6 +27,7 @@ import static tap.config.TAPConfiguration.KEY_MIN_LOG_LEVEL;
 import static tap.config.TAPConfiguration.KEY_OUTPUT_FORMATS;
 import static tap.config.TAPConfiguration.KEY_PROVIDER_NAME;
 import static tap.config.TAPConfiguration.KEY_SERVICE_DESCRIPTION;
+import static tap.config.TAPConfiguration.KEY_TAP_FACTORY;
 import static tap.config.TAPConfiguration.KEY_UDFS;
 import static tap.config.TAPConfiguration.KEY_UPLOAD_ENABLED;
 import static tap.config.TAPConfiguration.KEY_UPLOAD_MAX_FILE_SIZE;
@@ -93,7 +94,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 
 	private TAPLog logger;
 
-	private ConfigurableTAPFactory tapFactory;
+	private TAPFactory tapFactory;
 
 	private final TAPMetadata metadata;
 
@@ -136,7 +137,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		initLogger(tapConfig);
 
 		// 3. BUILD THE TAP FACTORY:
-		tapFactory = new ConfigurableTAPFactory(this, tapConfig);
+		initFactory(tapConfig);
 
 		// 4. GET THE METADATA:
 		metadata = initMetadata(tapConfig);
@@ -236,6 +237,14 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		logger.info(buf.toString());
 	}
 
+	private void initFactory(final Properties tapConfig) throws TAPException{
+		String propValue = getProperty(tapConfig, KEY_TAP_FACTORY);
+		if (propValue == null)
+			tapFactory = new ConfigurableTAPFactory(this, tapConfig);
+		else
+			tapFactory = newInstance(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ServiceConnection.class}, new Object[]{this});
+	}
+
 	private TAPMetadata initMetadata(final Properties tapConfig) throws TAPException{
 		// Get the fetching method to use:
 		String metaFetchType = getProperty(tapConfig, KEY_METADATA);
diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java
index 436ec66..f49fa7f 100644
--- a/src/tap/config/TAPConfiguration.java
+++ b/src/tap/config/TAPConfiguration.java
@@ -115,6 +115,9 @@ public final class TAPConfiguration {
 	/* ADDITIONAL TAP RESOURCES */
 	public final static String KEY_ADD_TAP_RESOURCES = "additional_resources";
 
+	/* CUSTOM FACTORY */
+	public final static String KEY_TAP_FACTORY = "tap_factory";
+
 	/**
 	 * <p>Read the asked property from the given Properties object.</p>
 	 * <ul>
diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html
index 091ad61..fd8ab7c 100644
--- a/src/tap/config/tap_configuration_file.html
+++ b/src/tap/config/tap_configuration_file.html
@@ -146,7 +146,7 @@
 				<td></td>
 			</tr>
 			
-			<tr><td colspan="5">Database</td></tr>
+			<tr><td colspan="5">Database (only if tap_factory = ø)</td></tr>
 			<tr class="mandatory">
 				<td class="done">database_access</td>
 				<td></td>
@@ -374,7 +374,7 @@
 				<td><ul><li>D 6 30</li><li>W 2 6 30</li><li>M 2 6 30</li><li>H 10</li><li>m</li></ul></td>
 			</tr>
 			
-			<tr><td colspan="5">UWS Backup</td></tr>
+			<tr><td colspan="5">UWS Backup (only if tap_factory = ø)</td></tr>
 			<tr class="optional">
 				<td class="done">backup_frequency</td>
 				<td></td>
@@ -622,6 +622,23 @@
 				<td>{aPackage.QuickADQLValidator}</td>
 			</tr>
 			
+			<tr><td colspan="5">Custom TAP Factory</td></tr>
+			<tr class="optional">
+				<td class="done">tap_factory</td>
+				<td></td>
+				<td>text</td>
+				<td>
+					<p>Class to use in replacement of the default TAPFactory.</p>
+					<p>
+						This property must be a class name (given between {...}). It must reference an extension of the abstract TAPFactory.
+						This extension must have at least one constructor with exactly one parameter of type ServiceConnection.
+					</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>
+				</td>
+				<td>{aPackage.MyTAPFactory}</td>
+			</tr>
+			
 		</table>
 		<script type="text/javascript">
 			var nb = document.getElementsByClassName("mandatory").length;
diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties
index f89fb2f..666f8da 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.0                                       #
-# Date: 18 Feb. 2015                                     #
+# Date: 19 Feb. 2015                                     #
 # Author: Gregory Mantelet (ARI)                         #
 #                                                        #
 ########################################################## 
@@ -463,4 +463,18 @@ udfs =
 # name (e.g. if getName() returns "sync", the /sync resource won't be anymore the default Sync resource of this library but your new resource).
 # 
 # By default, this list is empty ; only the standard TAP resources exist.
-additional_resources = 
\ No newline at end of file
+additional_resources = 
+
+######################
+# CUSTOM TAP_FACTORY #
+######################
+
+# [OPTIONAL]
+# Class to use in replacement of the default TAPFactory.
+# 
+# This property must be a class name (given between {...}). It must reference an extension of the abstract TAPFactory.
+# This extension must have at least one constructor with exactly one parameter of type ServiceConnection.
+# 
+# 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.
+tap_factory = 
diff --git a/test/tap/config/TestConfigurableServiceConnection.java b/test/tap/config/TestConfigurableServiceConnection.java
index 11bfd22..d232be8 100644
--- a/test/tap/config/TestConfigurableServiceConnection.java
+++ b/test/tap/config/TestConfigurableServiceConnection.java
@@ -17,6 +17,7 @@ import static tap.config.TAPConfiguration.KEY_METADATA;
 import static tap.config.TAPConfiguration.KEY_METADATA_FILE;
 import static tap.config.TAPConfiguration.KEY_MIN_LOG_LEVEL;
 import static tap.config.TAPConfiguration.KEY_OUTPUT_FORMATS;
+import static tap.config.TAPConfiguration.KEY_TAP_FACTORY;
 import static tap.config.TAPConfiguration.KEY_UDFS;
 import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER;
 import static tap.config.TAPConfiguration.VALUE_ANY;
@@ -44,9 +45,13 @@ import javax.servlet.http.HttpServletRequest;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import tap.AbstractTAPFactory;
 import tap.ServiceConnection;
 import tap.ServiceConnection.LimitUnit;
 import tap.TAPException;
+import tap.db.DBConnection;
+import tap.db.DBException;
+import tap.db.JDBCConnection;
 import tap.formatter.OutputFormat;
 import tap.formatter.VOTableFormat;
 import uk.ac.starlink.votable.DataFormat;
@@ -61,6 +66,7 @@ import uws.service.log.DefaultUWSLog;
 import uws.service.log.UWSLog.LogLevel;
 import adql.db.FunctionDef;
 import adql.db.TestDBChecker.UDFToto;
+import adql.translator.PostgreSQLTranslator;
 
 public class TestConfigurableServiceConnection {
 
@@ -83,7 +89,8 @@ public class TestConfigurableServiceConnection {
 			udfsListWithNONEorANYProp, udfsWithWrongParamLengthProp,
 			udfsWithMissingBracketsProp, udfsWithMissingDefProp1,
 			udfsWithMissingDefProp2, emptyUdfItemProp1, emptyUdfItemProp2,
-			udfWithMissingEndBracketProp;
+			udfWithMissingEndBracketProp, customFactoryProp,
+			badCustomFactoryProp;
 
 	@BeforeClass
 	public static void setUp() throws Exception{
@@ -242,6 +249,12 @@ public class TestConfigurableServiceConnection {
 
 		udfWithMissingEndBracketProp = (Properties)validProp.clone();
 		udfWithMissingEndBracketProp.setProperty(KEY_UDFS, "[toto(a string)->VARCHAR");
+
+		customFactoryProp = (Properties)validProp.clone();
+		customFactoryProp.setProperty(KEY_TAP_FACTORY, "{tap.config.TestConfigurableServiceConnection$CustomTAPFactory}");
+
+		badCustomFactoryProp = (Properties)validProp.clone();
+		badCustomFactoryProp.setProperty(KEY_TAP_FACTORY, "{tap.config.TestConfigurableServiceConnection$BadCustomTAPFactory}");
 	}
 
 	/**
@@ -855,6 +868,24 @@ public class TestConfigurableServiceConnection {
 			assertEquals(TAPException.class, e.getClass());
 			assertEquals("Wrong UDF declaration syntax: missing closing bracket at position 24!", e.getMessage());
 		}
+
+		// Valid custom TAPFactory:
+		try{
+			ServiceConnection connection = new ConfigurableServiceConnection(customFactoryProp);
+			assertNotNull(connection.getFactory());
+			assertEquals(CustomTAPFactory.class, connection.getFactory().getClass());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given custom TAPFactory exists and have the required constructor! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// Bad custom TAPFactory (required constructor missing):
+		try{
+			new ConfigurableServiceConnection(badCustomFactoryProp);
+			fail("This MUST have failed because the specified TAPFactory extension does not have a constructor with ServiceConnection!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Missing constructor tap.config.TestConfigurableServiceConnection$BadCustomTAPFactory(tap.ServiceConnection)! See the value \"{tap.config.TestConfigurableServiceConnection$BadCustomTAPFactory}\" of the property \"" + KEY_TAP_FACTORY + "\".", e.getMessage());
+		}
 	}
 
 	public static final String getPertinentMessage(final Exception ex){
@@ -898,4 +929,61 @@ public class TestConfigurableServiceConnection {
 
 	}
 
+	/**
+	 * TAPFactory just to test whether the property tap_factory works well.
+	 * 
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 02/2015
+	 */
+	private static class CustomTAPFactory extends AbstractTAPFactory {
+
+		private final JDBCConnection dbConn;
+
+		public CustomTAPFactory(final ServiceConnection conn) throws DBException{
+			super(conn);
+			dbConn = new JDBCConnection("", "jdbc:postgresql:gmantele", "gmantele", null, new PostgreSQLTranslator(), "TheOnlyConnection", conn.getLogger());
+		}
+
+		@Override
+		public DBConnection getConnection(final String jobID) throws TAPException{
+			return dbConn;
+		}
+
+		@Override
+		public void freeConnection(final DBConnection conn){}
+
+		@Override
+		public void destroy(){
+			try{
+				dbConn.getInnerConnection().close();
+			}catch(Exception ex){}
+		}
+
+	}
+
+	/**
+	 * TAPFactory just to test whether the property tap_factory is rejected when no constructor with a single parameter of type ServiceConnection exists.
+	 * 
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 02/2015
+	 */
+	private static class BadCustomTAPFactory extends AbstractTAPFactory {
+
+		public BadCustomTAPFactory() throws DBException{
+			super(null);
+		}
+
+		@Override
+		public DBConnection getConnection(final String jobID) throws TAPException{
+			return null;
+		}
+
+		@Override
+		public void freeConnection(final DBConnection conn){}
+
+		@Override
+		public void destroy(){}
+
+	}
+
 }
-- 
GitLab