From d708278c84121f2f9b0bf7139af72fca612a4f67 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Thu, 22 Oct 2015 12:48:58 +0200
Subject: [PATCH] [TAP] Add the possibility to wrap a TAPMetadata instance
 created automatically by the TAP configuration file in order to
 add/remove/change some metadata or to change the output of the TAP resource
 '/tables'.

---
 .../config/ConfigurableServiceConnection.java |  54 +++++++-
 src/tap/config/tap_configuration_file.html    |   8 +-
 src/tap/config/tap_full.properties            |  11 +-
 src/tap/config/tap_min.properties             |  11 +-
 src/tap/metadata/TAPMetadata.java             |  12 +-
 .../TestConfigurableServiceConnection.java    | 118 ++++++++++++++----
 6 files changed, 175 insertions(+), 39 deletions(-)

diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java
index 058740a..9ed6d13 100644
--- a/src/tap/config/ConfigurableServiceConnection.java
+++ b/src/tap/config/ConfigurableServiceConnection.java
@@ -124,7 +124,7 @@ import adql.query.operand.function.UserDefinedFunction;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (ARI)
- * @version 2.0 (04/2015)
+ * @version 2.1 (10/2015)
  * @since 2.0
  */
 public final class ConfigurableServiceConnection implements ServiceConnection {
@@ -435,7 +435,19 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		// Get the fetching method to use:
 		String metaFetchType = getProperty(tapConfig, KEY_METADATA);
 		if (metaFetchType == null)
-			throw new TAPException("The property \"" + KEY_METADATA + "\" is missing! It is required to create a TAP Service. Three possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document), " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA) or the name (between {}) of a class extending TAPMetadata.");
+			throw new TAPException("The property \"" + KEY_METADATA + "\" is missing! It is required to create a TAP Service. Three possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document), " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA) or the name (between {}) of a class extending TAPMetadata. Only " + VALUE_XML + " and " + VALUE_DB + " can be followed by the path of a class extending TAPMetadata.");
+
+		// Extract a custom class suffix if any for XML and DB options:
+		String customMetaClass = null;
+		if (metaFetchType.toLowerCase().matches("(" + VALUE_XML + "|" + VALUE_DB + ").*")){
+			int indSep = metaFetchType.toLowerCase().startsWith(VALUE_XML) ? 3 : 2;
+			customMetaClass = metaFetchType.substring(indSep).trim();
+			metaFetchType = metaFetchType.substring(0, indSep);
+			if (customMetaClass.length() == 0)
+				customMetaClass = null;
+			else if (!isClassName(customMetaClass))
+				throw new TAPException("Unexpected string after the fetching method \"" + metaFetchType + "\": \"" + customMetaClass + "\"! The full name of a class extending TAPMetadata was expected. If it is a class name, then it must be specified between {}.");
+		}
 
 		TAPMetadata metadata = null;
 
@@ -522,7 +534,43 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		}
 		// INCORRECT VALUE => ERROR!
 		else
-			throw new TAPException("Unsupported value for the property \"" + KEY_METADATA + "\": \"" + metaFetchType + "\"! Only two values are allowed: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).");
+			throw new TAPException("Unsupported value for the property \"" + KEY_METADATA + "\": \"" + metaFetchType + "\"! Only two values are allowed: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA). Only " + VALUE_XML + " and " + VALUE_DB + " can be followed by the path of a class extending TAPMetadata.");
+
+		// Create the custom TAPMetadata extension if any is provided (THEORETICALLY, JUST FOR XML and DB):
+		if (customMetaClass != null){
+			// get the class:
+			Class<? extends TAPMetadata> metaClass = fetchClass(customMetaClass, KEY_METADATA, TAPMetadata.class);
+			if (metaClass == TAPMetadata.class)
+				throw new TAPException("Wrong class for the property \"" + KEY_METADATA + "\": \"" + metaClass.getName() + "\"! The class provided in this property MUST EXTEND tap.metadata.TAPMetadata.");
+			try{
+				// get one of the expected constructors:
+				try{
+					// (TAPMetadata, UWSFileManager, TAPFactory, TAPLog):
+					Constructor<? extends TAPMetadata> constructor = metaClass.getConstructor(TAPMetadata.class, UWSFileManager.class, TAPFactory.class, TAPLog.class);
+					// create the TAP metadata:
+					metadata = constructor.newInstance(metadata, fileManager, tapFactory, logger);
+				}catch(NoSuchMethodException nsme){
+					// (TAPMetadata):
+					Constructor<? extends TAPMetadata> constructor = metaClass.getConstructor(TAPMetadata.class);
+					// create the TAP metadata:
+					metadata = constructor.newInstance(metadata);
+				}
+			}catch(NoSuchMethodException nsme){
+				throw new TAPException("Missing constructor by copy tap.metadata.TAPMetadata(tap.metadata.TAPMetadata) or tap.metadata.TAPMetadata(tap.metadata.TAPMetadata, uws.service.file.UWSFileManager, tap.TAPFactory, tap.log.TAPLog)! See the value \"" + metaFetchType + "\" of the property \"" + KEY_METADATA + "\".");
+			}catch(InstantiationException ie){
+				throw new TAPException("Impossible to create an instance of an abstract class: \"" + metaClass.getName() + "\"! See the value \"" + metaFetchType + "\" of the property \"" + KEY_METADATA + "\".");
+			}catch(InvocationTargetException ite){
+				if (ite.getCause() != null){
+					if (ite.getCause() instanceof TAPException)
+						throw (TAPException)ite.getCause();
+					else
+						throw new TAPException(ite.getCause());
+				}else
+					throw new TAPException(ite);
+			}catch(Exception ex){
+				throw new TAPException("Impossible to create an instance of tap.metadata.TAPMetadata as specified in the property \"" + KEY_METADATA + "\": \"" + metaFetchType + "\"!", ex);
+			}
+		}
 
 		return metadata;
 	}
diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html
index 0bebdc9..13e45ff 100644
--- a/src/tap/config/tap_configuration_file.html
+++ b/src/tap/config/tap_configuration_file.html
@@ -280,8 +280,14 @@
 						<li>Build yourself the metadata of your service by creating an extension of tap.metadata.TAPMetadata. This extension must have either an empty constructor
 							or a constructor with exactly 3 parameters of type UWSFileManager, TAPFactory and TAPLog ; if both constructor are provided, only the one with parameters will be used.</li>
 					</ol>
+					<p>
+						For the two first methods, it is also possible to specify an extension of tap.metadata.TAPMetadata which will wrap a default TAPMetadata objects created using the specified
+						methods (i.e. XML tableset or TAP_SCHEMA). In this way, it is possible to get the "default" metadata from an XML file or the database
+						and then add/remove/modify some of them, or to change the output of the 'tables' resource. The extension of tap.metadata.TAPMetadata must have at least
+						one constructor with the following parameters: (TAPMetadata) or (TAPMetadata, UWSFileManager, TAPFactory, TAPLog).
+					</p>
 				</td>
-				<td><ul><li>xml</li><li>db</li><li>{apackage.MyTAPMetadata}</li></ul>
+				<td><ul><li>xml</li><li>xml {myTAPMetadata}</li><li>db</li><li>db {myTAPMetadata}</li><li>{apackage.MyTAPMetadata}</li></ul>
 			</tr>
 			<tr class="optional">
 				<td class="done">metadata_file</td>
diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties
index b223d59..78c1bb7 100644
--- a/src/tap/config/tap_full.properties
+++ b/src/tap/config/tap_full.properties
@@ -1,8 +1,8 @@
 ##########################################################
 #             FULL TAP CONFIGURATION FILE                #
 #                                                        #
-# TAP Version: 2.0                                       #
-# Date: 13 April 2015                                    #
+# TAP Version: 2.1                                       #
+# Date: 22 Oct. 2015                                     #
 # Author: Gregory Mantelet (ARI)                         #
 #                                                        #
 ########################################################## 
@@ -151,8 +151,13 @@ db_password =
 #    2/ Get all metadata from the database schema TAP_SCHEMA.
 #    3/ Build yourself the metadata of your service by creating an extension of tap.metadata.TAPMetadata. This extension must have either an empty constructor
 #       or a constructor with exactly 3 parameters of type UWSFileManager, TAPFactory and TAPLog ; if both constructor are provided, only the one with parameters will be used.
+# 
+# For the two first methods, it is also possible to specify an extension of tap.metadata.TAPMetadata which will wrap a default TAPMetadata objects created using the specified
+# methods (i.e. XML tableset or TAP_SCHEMA). In this way, it is possible to get the "default" metadata from an XML file or the database
+# and then add/remove/modify some of them, or to change the output of the 'tables' resource. The extension of tap.metadata.TAPMetadata must have at least
+# one constructor with the following parameters: (TAPMetadata) or (TAPMetadata, UWSFileManager, TAPFactory, TAPLog).
 #  
-# Allowed values: xml, db or a full class name (between {}).
+# Allowed values: xml, xml {myTAPMetadata}, db, db {myTAPMetadata} or a full class name (between {}).
 metadata =  
 
 # [MANDATORY]
diff --git a/src/tap/config/tap_min.properties b/src/tap/config/tap_min.properties
index 2bfd5af..fd07970 100644
--- a/src/tap/config/tap_min.properties
+++ b/src/tap/config/tap_min.properties
@@ -1,8 +1,8 @@
 ##########################################################
 #            MINIMUM TAP CONFIGURATION FILE              #
 #                                                        #
-# TAP Version: 2.0                                       #
-# Date: 27 Feb. 2015                                     #
+# TAP Version: 2.1                                       #
+# Date: 22 Oct. 2015                                     #
 # Author: Gregory Mantelet (ARI)                         #
 #                                                        #
 ########################################################## 
@@ -79,8 +79,13 @@ db_password =
 #    2/ Get all metadata from the database schema TAP_SCHEMA.
 #    3/ Build yourself the metadata of your service by creating an extension of tap.metadata.TAPMetadata. This extension must have either an empty constructor
 #       or a constructor with exactly 3 parameters of type UWSFileManager, TAPFactory and TAPLog ; if both constructor are provided, only the one with parameters will be used.
+# 
+# For the two first methods, it is also possible to specify an extension of tap.metadata.TAPMetadata which will wrap a default TAPMetadata objects created using the specified
+# methods (i.e. XML tableset or TAP_SCHEMA). In this way, it is possible to get the "default" metadata from an XML file or the database
+# and then add/remove/modify some of them, or to change the output of the 'tables' resource. The extension of tap.metadata.TAPMetadata must have at least
+# one constructor with the following parameters: (TAPMetadata) or (TAPMetadata, UWSFileManager, TAPFactory, TAPLog).
 #  
-# Allowed values: xml, db or a full class name (between {}).
+# Allowed values: xml, xml {myTAPMetadata}, db, db {myTAPMetadata} or a full class name (between {}).
 metadata =  
 
 # Mandatory if the value of "metadata" is "xml".
diff --git a/src/tap/metadata/TAPMetadata.java b/src/tap/metadata/TAPMetadata.java
index 580b966..8173e3f 100644
--- a/src/tap/metadata/TAPMetadata.java
+++ b/src/tap/metadata/TAPMetadata.java
@@ -64,7 +64,7 @@ import adql.db.DBType.DBDatatype;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (03/2015)
+ * @version 2.1 (10/2015)
  */
 public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResource {
 
@@ -514,7 +514,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * 
 	 * @see #writeTable(TAPTable, PrintWriter)
 	 */
-	private void writeSchema(TAPSchema s, PrintWriter writer) throws IOException{
+	protected void writeSchema(TAPSchema s, PrintWriter writer) throws IOException{
 		final String prefix = "\t\t";
 		writer.println("\t<schema>");
 
@@ -577,7 +577,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * 
 	 * @return	The total number of written columns.
 	 */
-	private int writeTable(TAPTable t, PrintWriter writer){
+	protected int writeTable(TAPTable t, PrintWriter writer){
 		final String prefix = "\t\t\t";
 
 		writer.print("\t\t<table");
@@ -635,7 +635,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * @param c			The column to format and to write in XML.
 	 * @param writer	Output in which the XML serialization of the given column must be written.
 	 */
-	private void writeColumn(TAPColumn c, PrintWriter writer){
+	protected void writeColumn(TAPColumn c, PrintWriter writer){
 		final String prefix = "\t\t\t\t";
 
 		writer.print("\t\t\t<column");
@@ -696,7 +696,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 * @param fk		The foreign key to format and to write in XML.
 	 * @param writer	Output in which the XML serialization of the given foreign key must be written.
 	 */
-	private void writeForeignKey(TAPForeignKey fk, PrintWriter writer){
+	protected void writeForeignKey(TAPForeignKey fk, PrintWriter writer){
 		final String prefix = "\t\t\t\t";
 
 		writer.println("\t\t\t<foreignKey>");
@@ -728,7 +728,7 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour
 	 *                      	<i>false</i> otherwise (here, if the value is NULL or an empty string, the XML item will be written with an empty string as value). 
 	 * @param writer			Output in which the XML node must be written.
 	 */
-	private void writeAtt(String prefix, String attributeName, String attributeValue, boolean isOptionalAttr, PrintWriter writer){
+	protected final void writeAtt(String prefix, String attributeName, String attributeValue, boolean isOptionalAttr, PrintWriter writer){
 		if (attributeValue != null && attributeValue.trim().length() > 0){
 			StringBuffer xml = new StringBuffer(prefix);
 			xml.append('<').append(attributeName).append('>').append(VOSerializer.formatText(attributeValue)).append("</").append(attributeName).append('>');
diff --git a/test/tap/config/TestConfigurableServiceConnection.java b/test/tap/config/TestConfigurableServiceConnection.java
index f2058cf..ac62179 100644
--- a/test/tap/config/TestConfigurableServiceConnection.java
+++ b/test/tap/config/TestConfigurableServiceConnection.java
@@ -59,6 +59,8 @@ import tap.db.DBException;
 import tap.db.JDBCConnection;
 import tap.formatter.OutputFormat;
 import tap.formatter.VOTableFormat;
+import tap.metadata.TAPMetadata;
+import tap.metadata.TAPSchema;
 import uk.ac.starlink.votable.DataFormat;
 import uk.ac.starlink.votable.VOTableVersion;
 import uws.UWSException;
@@ -82,27 +84,28 @@ public class TestConfigurableServiceConnection {
 
 	private static Properties validProp, noFmProp, fmClassNameProp,
 			incorrectFmProp, correctLogProp, incorrectLogLevelProp,
-			incorrectLogRotationProp, xmlMetaProp, wrongManualMetaProp,
-			missingMetaProp, missingMetaFileProp, wrongMetaProp,
-			wrongMetaFileProp, validFormatsProp, validVOTableFormatsProp,
-			badSVFormat1Prop, badSVFormat2Prop, badVotFormat1Prop,
-			badVotFormat2Prop, badVotFormat3Prop, badVotFormat4Prop,
-			badVotFormat5Prop, badVotFormat6Prop, unknownFormatProp,
-			maxAsyncProp, negativeMaxAsyncProp, notIntMaxAsyncProp,
-			defaultOutputLimitProp, maxOutputLimitProp,
-			bothOutputLimitGoodProp, bothOutputLimitBadProp, syncFetchSizeProp,
-			notIntSyncFetchSizeProp, negativeSyncFetchSizeProp,
-			notIntAsyncFetchSizeProp, negativeAsyncFetchSizeProp,
-			asyncFetchSizeProp, userIdentProp, notClassPathUserIdentProp,
-			coordSysProp, noneCoordSysProp, anyCoordSysProp,
-			noneInsideCoordSysProp, unknownCoordSysProp, geometriesProp,
-			noneGeomProp, anyGeomProp, noneInsideGeomProp, unknownGeomProp,
-			anyUdfsProp, noneUdfsProp, udfsProp, udfsWithClassNameProp,
-			udfsListWithNONEorANYProp, udfsWithWrongParamLengthProp,
-			udfsWithMissingBracketsProp, udfsWithMissingDefProp1,
-			udfsWithMissingDefProp2, emptyUdfItemProp1, emptyUdfItemProp2,
-			udfWithMissingEndBracketProp, customFactoryProp,
-			badCustomFactoryProp;
+			incorrectLogRotationProp, xmlMetaProp,
+			xmlMetaPropWithCustomMetaClass, xmlMetaPropWithBadCustomMetaClass,
+			xmlMetaPropWithANonMetaClass, wrongManualMetaProp, missingMetaProp,
+			missingMetaFileProp, wrongMetaProp, wrongMetaFileProp,
+			validFormatsProp, validVOTableFormatsProp, badSVFormat1Prop,
+			badSVFormat2Prop, badVotFormat1Prop, badVotFormat2Prop,
+			badVotFormat3Prop, badVotFormat4Prop, badVotFormat5Prop,
+			badVotFormat6Prop, unknownFormatProp, maxAsyncProp,
+			negativeMaxAsyncProp, notIntMaxAsyncProp, defaultOutputLimitProp,
+			maxOutputLimitProp, bothOutputLimitGoodProp,
+			bothOutputLimitBadProp, syncFetchSizeProp, notIntSyncFetchSizeProp,
+			negativeSyncFetchSizeProp, notIntAsyncFetchSizeProp,
+			negativeAsyncFetchSizeProp, asyncFetchSizeProp, userIdentProp,
+			notClassPathUserIdentProp, coordSysProp, noneCoordSysProp,
+			anyCoordSysProp, noneInsideCoordSysProp, unknownCoordSysProp,
+			geometriesProp, noneGeomProp, anyGeomProp, noneInsideGeomProp,
+			unknownGeomProp, anyUdfsProp, noneUdfsProp, udfsProp,
+			udfsWithClassNameProp, udfsListWithNONEorANYProp,
+			udfsWithWrongParamLengthProp, udfsWithMissingBracketsProp,
+			udfsWithMissingDefProp1, udfsWithMissingDefProp2,
+			emptyUdfItemProp1, emptyUdfItemProp2, udfWithMissingEndBracketProp,
+			customFactoryProp, badCustomFactoryProp;
 
 	@BeforeClass
 	public static void setUp() throws Exception{
@@ -132,6 +135,18 @@ public class TestConfigurableServiceConnection {
 		xmlMetaProp.setProperty(KEY_METADATA, VALUE_XML);
 		xmlMetaProp.setProperty(KEY_METADATA_FILE, XML_FILE);
 
+		xmlMetaPropWithCustomMetaClass = (Properties)validProp.clone();
+		xmlMetaPropWithCustomMetaClass.setProperty(KEY_METADATA, VALUE_XML + " {tap.config.TestConfigurableServiceConnection$MyCustomTAPMetadata}");
+		xmlMetaPropWithCustomMetaClass.setProperty(KEY_METADATA_FILE, XML_FILE);
+
+		xmlMetaPropWithBadCustomMetaClass = (Properties)validProp.clone();
+		xmlMetaPropWithBadCustomMetaClass.setProperty(KEY_METADATA, VALUE_XML + " {tap.config.TestConfigurableServiceConnection$MyBadCustomTAPMetadata}");
+		xmlMetaPropWithBadCustomMetaClass.setProperty(KEY_METADATA_FILE, XML_FILE);
+
+		xmlMetaPropWithANonMetaClass = (Properties)validProp.clone();
+		xmlMetaPropWithANonMetaClass.setProperty(KEY_METADATA, VALUE_XML + " MyCustomTAPMetadata");
+		xmlMetaPropWithANonMetaClass.setProperty(KEY_METADATA_FILE, XML_FILE);
+
 		wrongManualMetaProp = (Properties)validProp.clone();
 		wrongManualMetaProp.setProperty(KEY_METADATA, "{tap.metadata.TAPMetadata}");
 
@@ -395,7 +410,7 @@ public class TestConfigurableServiceConnection {
 			fail("This MUST have failed because the property 'metadata' is missing!");
 		}catch(Exception e){
 			assertEquals(TAPException.class, e.getClass());
-			assertEquals("The property \"" + KEY_METADATA + "\" is missing! It is required to create a TAP Service. Three possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document), " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA) or the name (between {}) of a class extending TAPMetadata.", e.getMessage());
+			assertEquals("The property \"" + KEY_METADATA + "\" is missing! It is required to create a TAP Service. Three possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document), " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA) or the name (between {}) of a class extending TAPMetadata. Only " + VALUE_XML + " and " + VALUE_DB + " can be followed by the path of a class extending TAPMetadata.", e.getMessage());
 		}
 
 		// Missing metadata_file property:
@@ -413,7 +428,7 @@ public class TestConfigurableServiceConnection {
 			fail("This MUST have failed because the property 'metadata' has a wrong value!");
 		}catch(Exception e){
 			assertEquals(TAPException.class, e.getClass());
-			assertEquals("Unsupported value for the property \"" + KEY_METADATA + "\": \"foo\"! Only two values are allowed: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).", e.getMessage());
+			assertEquals("Unsupported value for the property \"" + KEY_METADATA + "\": \"foo\"! Only two values are allowed: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA). Only " + VALUE_XML + " and " + VALUE_DB + " can be followed by the path of a class extending TAPMetadata.", e.getMessage());
 		}
 
 		// Wrong MANUAL metadata:
@@ -425,6 +440,33 @@ public class TestConfigurableServiceConnection {
 			assertEquals("Wrong class for the property \"" + KEY_METADATA + "\": \"tap.metadata.TAPMetadata\"! The class provided in this property MUST EXTEND tap.metadata.TAPMetadata.", e.getMessage());
 		}
 
+		// XML metadata method WITH a custom TAPMetadata class:
+		try{
+			ServiceConnection sConn = new ConfigurableServiceConnection(xmlMetaPropWithCustomMetaClass);
+			assertEquals(MyCustomTAPMetadata.class, sConn.getTAPMetadata().getClass());
+		}catch(Exception e){
+			e.printStackTrace();
+			fail("This MUST have succeeded because the property 'metadata' is valid! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// XML metadata method WITH a BAD custom TAPMetadata class:
+		try{
+			new ConfigurableServiceConnection(xmlMetaPropWithBadCustomMetaClass);
+			fail("This MUST have failed because the custom class specified in the property 'metadata' does not have any of the required constructor!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Missing constructor by copy tap.metadata.TAPMetadata(tap.metadata.TAPMetadata) or tap.metadata.TAPMetadata(tap.metadata.TAPMetadata, uws.service.file.UWSFileManager, tap.TAPFactory, tap.log.TAPLog)! See the value \"xml\" of the property \"" + KEY_METADATA + "\".", e.getMessage());
+		}
+
+		// XML metadata method WITH a BAD custom TAPMetadata class:
+		try{
+			new ConfigurableServiceConnection(xmlMetaPropWithANonMetaClass);
+			fail("This MUST have failed because the class specified in the property 'metadata' is not a class name!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Unexpected string after the fetching method \"xml\": \"MyCustomTAPMetadata\"! The full name of a class extending TAPMetadata was expected. If it is a class name, then it must be specified between {}.", e.getMessage());
+		}
+
 		// Wrong metadata_file property:
 		try{
 			new ConfigurableServiceConnection(wrongMetaFileProp);
@@ -1197,4 +1239,34 @@ public class TestConfigurableServiceConnection {
 
 	}
 
+	/**
+	 * TAPMetadata extension just to test whether it is possible to customize the output class of ConfigurableServiceConnection with the
+	 * metadata fetching methods "db" and "xml".
+	 * 
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 08/2015
+	 */
+	private static class MyCustomTAPMetadata extends TAPMetadata {
+		public MyCustomTAPMetadata(TAPMetadata meta){
+			for(TAPSchema s : meta){
+				this.addSchema(s);
+			}
+		}
+	}
+
+	/**
+	 * TAPMetadata extension just to test whether it is possible to customize the output class of ConfigurableServiceConnection with the
+	 * metadata fetching methods "db" and "xml".
+	 * 
+	 * <strong>This extension is however bad because it does not have any of the required constructor.</strong>
+	 * 
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 08/2015
+	 */
+	private static class MyBadCustomTAPMetadata extends TAPMetadata {
+		public MyBadCustomTAPMetadata(){
+
+		}
+	}
+
 }
-- 
GitLab