Skip to content
Snippets Groups Projects
Commit d708278c authored by gmantele's avatar gmantele
Browse files

[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'.
parent f8978304
No related branches found
No related tags found
No related merge requests found
......@@ -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;
}
......
......@@ -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>
......
##########################################################
# FULL TAP CONFIGURATION FILE #
# #
# TAP Version: 2.0 #
# Date: 13 April 2015 #
# TAP Version: 2.1 #
# Date: 22 Oct. 2015 #
# Author: Gregory Mantelet (ARI) #
# #
##########################################################
......@@ -152,7 +152,12 @@ db_password =
# 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.
#
# Allowed values: xml, db or a full class name (between {}).
# 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, xml {myTAPMetadata}, db, db {myTAPMetadata} or a full class name (between {}).
metadata =
# [MANDATORY]
......
##########################################################
# MINIMUM TAP CONFIGURATION FILE #
# #
# TAP Version: 2.0 #
# Date: 27 Feb. 2015 #
# TAP Version: 2.1 #
# Date: 22 Oct. 2015 #
# Author: Gregory Mantelet (ARI) #
# #
##########################################################
......@@ -80,7 +80,12 @@ db_password =
# 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.
#
# Allowed values: xml, db or a full class name (between {}).
# 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, xml {myTAPMetadata}, db, db {myTAPMetadata} or a full class name (between {}).
metadata =
# Mandatory if the value of "metadata" is "xml".
......
......@@ -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('>');
......
......@@ -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(){
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment