diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java index f7e7c48955b2b2e6033f33c1d54743482d16f24f..1e178ed1945f8d920d1c1c734d0480a11961fced 100644 --- a/src/tap/config/ConfigurableServiceConnection.java +++ b/src/tap/config/ConfigurableServiceConnection.java @@ -49,6 +49,7 @@ import static tap.config.TAPConfiguration.VALUE_XML; import static tap.config.TAPConfiguration.fetchClass; import static tap.config.TAPConfiguration.getProperty; import static tap.config.TAPConfiguration.isClassPath; +import static tap.config.TAPConfiguration.newInstance; import static tap.config.TAPConfiguration.parseLimit; import java.io.File; @@ -123,6 +124,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection { private Collection<FunctionDef> udfs = new ArrayList<FunctionDef>(0); public ConfigurableServiceConnection(final Properties tapConfig) throws NullPointerException, TAPException, UWSException{ + if (tapConfig == null) + throw new NullPointerException("Missing TAP properties! "); + // 1. INITIALIZE THE FILE MANAGER: initFileManager(tapConfig); @@ -198,20 +202,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection { } } // CUSTOM file manager: - else{ - Class<? extends UWSFileManager> classObj = fetchClass(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class); - if (classObj == null) - throw new TAPException("Unknown value for the property \"" + KEY_FILE_MANAGER + "\": \"" + fileManagerType + "\". Only two possible values: " + VALUE_LOCAL + " or a class path between {...}."); - - try{ - fileManager = classObj.getConstructor(Properties.class).newInstance(tapConfig); - }catch(Exception e){ - if (e instanceof TAPException) - throw (TAPException)e; - else - throw new TAPException("Impossible to create a TAPFileManager instance with the constructor (java.util.Properties tapConfig) of \"" + classObj.getName() + "\" for the following reason: " + e.getMessage()); - } - } + else + fileManager = newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{Properties.class}, new Object[]{tapConfig}); } private void initLogger(final Properties tapConfig){ @@ -458,18 +450,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection { hasVotableFormat = true; } // custom OutputFormat - else if (isClassPath(f)){ - Class<? extends OutputFormat> userOutputFormatClass = fetchClass(f, KEY_OUTPUT_FORMATS, OutputFormat.class); - try{ - OutputFormat userOutputFormat = userOutputFormatClass.getConstructor(ServiceConnection.class).newInstance(this); - outputFormats.add(userOutputFormat); - }catch(Exception e){ - if (e instanceof TAPException) - throw (TAPException)e; - else - throw new TAPException("Impossible to create an OutputFormat instance with the constructor (ServiceConnection) of \"" + userOutputFormatClass.getName() + "\" (see the property output_add_format) for the following reason: " + e.getMessage()); - } - } + else if (isClassPath(f)) + outputFormats.add(TAPConfiguration.newInstance(f, KEY_OUTPUT_FORMATS, OutputFormat.class, new Class<?>[]{ServiceConnection.class}, new Object[]{this})); // unknown format else throw new TAPException("Unknown output format: " + f); diff --git a/src/tap/config/ConfigurableTAPFactory.java b/src/tap/config/ConfigurableTAPFactory.java index 6fd9c669767c89f556ea4728fb313048ff0b1b12..40edfab4f8d661a852b78f5e45a3141c60924080 100644 --- a/src/tap/config/ConfigurableTAPFactory.java +++ b/src/tap/config/ConfigurableTAPFactory.java @@ -60,6 +60,9 @@ public final class ConfigurableTAPFactory extends AbstractTAPFactory { public ConfigurableTAPFactory(ServiceConnection service, final Properties tapConfig) throws NullPointerException, TAPException{ super(service); + if (tapConfig == null) + throw new NullPointerException("Missing TAP properties! "); + /* 1. Configure the database access */ final String dbAccessMethod = getProperty(tapConfig, KEY_DATABASE_ACCESS); diff --git a/src/tap/config/ConfigurableTAPServlet.java b/src/tap/config/ConfigurableTAPServlet.java index 8260eb382b9701a6b0fe7ca6bf0ff7f7a9bc897f..69aa12fcb7c5f03b0d360c8b922e7cd8f4a8ffc4 100644 --- a/src/tap/config/ConfigurableTAPServlet.java +++ b/src/tap/config/ConfigurableTAPServlet.java @@ -20,18 +20,18 @@ package tap.config; */ import static tap.config.TAPConfiguration.DEFAULT_TAP_CONF_FILE; +import static tap.config.TAPConfiguration.KEY_ADD_TAP_RESOURCES; import static tap.config.TAPConfiguration.KEY_HOME_PAGE; import static tap.config.TAPConfiguration.KEY_HOME_PAGE_MIME_TYPE; import static tap.config.TAPConfiguration.TAP_CONF_PARAMETER; -import static tap.config.TAPConfiguration.fetchClass; import static tap.config.TAPConfiguration.getProperty; import static tap.config.TAPConfiguration.isClassPath; +import static tap.config.TAPConfiguration.newInstance; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Constructor; import java.util.Properties; import javax.servlet.ServletConfig; @@ -44,6 +44,7 @@ import tap.ServiceConnection; import tap.TAPException; import tap.resource.HomePage; import tap.resource.TAP; +import tap.resource.TAPResource; public class ConfigurableTAPServlet extends HttpServlet { private static final long serialVersionUID = 1L; @@ -117,21 +118,10 @@ public class ConfigurableTAPServlet extends HttpServlet { if (propValue != null){ // If it is a class path, replace the current home page by an instance of this class: if (isClassPath(propValue)){ - Class<? extends HomePage> newHomePage = null; try{ - // ...fetch the class: - newHomePage = fetchClass(propValue, KEY_HOME_PAGE, HomePage.class); - // ...get its constructor with TAP object: - Constructor<? extends HomePage> constructor = newHomePage.getConstructor(TAP.class); - // ...create a new instance and set it as new home page: - tap.setHomePage(constructor.newInstance(tap)); - }catch(NoSuchMethodException e){ - throw new ServletException("Missing constructor " + (newHomePage == null ? "HomePage" : newHomePage.getName()) + "(TAP)! This constructor is required to set a new home page to your TAP service."); - }catch(Exception ex){ - if (ex instanceof TAPException) - throw new ServletException(ex.getMessage(), (ex.getCause() == null ? ex : ex.getCause())); - else - throw new ServletException("Impossible to set the specified home page: \"" + propValue + "\"!", ex); + tap.setHomePage(newInstance(propValue, KEY_HOME_PAGE, HomePage.class, new Class<?>[]{TAP.class}, new Object[]{tap})); + }catch(TAPException te){ + throw new ServletException(te.getMessage(), te.getCause()); } } // If it is a file URI (null, file inside WebContent, file://..., http://..., etc...): @@ -145,10 +135,33 @@ public class ConfigurableTAPServlet extends HttpServlet { } } - /* 5. DEFAULT SERVLET INITIALIZATION */ + /* 5. SET ADDITIONAL TAP RESOURCES */ + propValue = getProperty(tapConf, KEY_ADD_TAP_RESOURCES); + if (propValue != null){ + // split all list items: + String[] lstResources = propValue.split(","); + for(String addRes : lstResources){ + addRes = addRes.trim(); + // ignore empty items: + if (addRes.length() > 0){ + try{ + // create an instance of the resource: + TAPResource newRes = newInstance(addRes, KEY_ADD_TAP_RESOURCES, TAPResource.class, new Class<?>[]{TAP.class}, new Object[]{tap}); + if (newRes.getName() == null || newRes.getName().trim().length() == 0) + throw new TAPException("TAP resource name missing for the new resource \"" + addRes + "\"! The function getName() of the new TAPResource must return a non-empty and not NULL name. See the property \"" + KEY_ADD_TAP_RESOURCES + "\"."); + // add it into TAP: + tap.addResource(newRes); + }catch(TAPException te){ + throw new ServletException(te.getMessage(), te.getCause()); + } + } + } + } + + /* 6. DEFAULT SERVLET INITIALIZATION */ super.init(config); - /* 6. FINALLY MAKE THE SERVICE AVAILABLE */ + /* 7. FINALLY MAKE THE SERVICE AVAILABLE */ serviceConn.setAvailable(true, "TAP service available."); } diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java index 3a6976218926e5438857dd9e07c8511497f7e4b4..f1e5d9fcddbaf800d828753133a7bf3695d91e7e 100644 --- a/src/tap/config/TAPConfiguration.java +++ b/src/tap/config/TAPConfiguration.java @@ -2,6 +2,8 @@ package tap.config; import java.io.File; import java.io.FileInputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.HashMap; import java.util.Properties; @@ -113,6 +115,9 @@ public final class TAPConfiguration { public final static String KEY_UDFS = "udfs"; public final static String VALUE_ANY = "ANY"; + /* ADDITIONAL TAP RESOURCES */ + public final static String KEY_ADD_TAP_RESOURCES = "additional_resources"; + /** * <p>Read the asked property from the given Properties object.</p> * <ul> @@ -187,6 +192,99 @@ public final class TAPConfiguration { } } + /** + * <p>Create an instance of the specified class. The class name is expected to be surrounded by {} in the given value.</p> + * + * <p>The instance is created using the empty constructor of the specified class.</p> + * + * @param value Value which is supposed to contain the classpath between brackets (see {@link #isClassPath(String)} for more details) + * @param propertyName Name of the property associated with the parameter "value". + * @param expectedType Type of the class expected to be returned ; it is also the type which parameterizes this function: C. + * + * @return The corresponding instance. + * + * @throws TAPException If the class name is incorrect + * or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType") + * or if the specified class has no empty constructor + * or if an error occurred while calling this constructor. + * + * @see {@link #isClassPath(String)} + * @see #fetchClass(String, String, Class) + */ + public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType) throws TAPException{ + return newInstance(propValue, propName, expectedType, null, null); + } + + /** + * <p>Create an instance of the specified class. The class name is expected to be surrounded by {} in the given value.</p> + * + * <p><b>IMPORTANT:</b> + * The instance is created using the constructor whose the declaration matches exactly with the given list of parameter types. + * The number and types of given parameters MUST match exactly to the list of parameter types. + * </p> + * + * @param value Value which is supposed to contain the classpath between brackets (see {@link #isClassPath(String)} for more details) + * @param propertyName Name of the property associated with the parameter "value". + * @param expectedType Type of the class expected to be returned ; it is also the type which parameterizes this function: C. + * @param pTypes List of each constructor parameter type. Each type MUST be exactly the type declared in the class constructor to select. <i>NULL or empty array if no parameter.</i> + * @param parameters List of all constructor parameters. The number of object MUST match exactly the number of classes provided in the parameter pTypes. <i>NULL or empty array if no parameter.</i> + * + * @return The corresponding instance. + * + * @throws TAPException If the class name is incorrect + * or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType") + * or if the constructor with the specified parameters can not be found + * or if an error occurred while calling this constructor. + * + * @see {@link #isClassPath(String)} + * @see #fetchClass(String, String, Class) + */ + public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes, final Object[] parameters) throws TAPException{ + // Ensure the given name is a class name specification: + if (!isClassPath(propValue)) + throw new TAPException("Class name expected for the property \"" + propName + "\" instead of: \"" + propValue + "\"! The specified class must extend/implement " + expectedType.getName() + "."); + + Class<? extends C> classObj = null; + try{ + + // Fetch the class object: + classObj = fetchClass(propValue, propName, expectedType); + + // Get a constructor matching the given parameters list: + Constructor<? extends C> constructor = classObj.getConstructor((pTypes == null) ? new Class<?>[0] : pTypes); + + // Finally create a new instance: + return constructor.newInstance((parameters == null) ? new Object[0] : parameters); + + }catch(NoSuchMethodException e){ + // List parameters' type: + StringBuffer pTypesStr = new StringBuffer(); + for(int i = 0; i < pTypes.length; i++){ + if (pTypesStr.length() > 0) + pTypesStr.append(", "); + if (pTypes[i] == null) + pTypesStr.append("NULL"); + pTypesStr.append(pTypes[i].getName()); + } + // Throw the error: + throw new TAPException("Missing constructor " + classObj.getName() + "(" + pTypesStr.toString() + ")! See the value \"" + propValue + "\" of the property \"" + propName + "\"."); + }catch(InstantiationException ie){ + throw new TAPException("Impossible to create an instance of an abstract class: \"" + classObj.getName() + "\"! See the value \"" + propValue + "\" of the property \"" + propName + "\"."); + }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(TAPException te){ + throw te; + }catch(Exception ex){ + throw new TAPException("Impossible to create an instance of " + expectedType.getName() + " as specified in the property \"" + propName + "\": \"" + propValue + "\"!", ex); + } + } + /** * <p>Lets parsing a limit (for output, upload, ...) with its numeric value and its unit.</p> * <p> diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html index 08879eedd78e683b74111095f484951996d6e181..82e83a9592a29bf0a5b88f718ef13519c3abc4cf 100644 --- a/src/tap/config/tap_configuration_file.html +++ b/src/tap/config/tap_configuration_file.html @@ -595,6 +595,31 @@ <td><ul><li>ΓΈ <em>(default)</em></li><li>ANY</li><li>[trim(txt String) -> String], [random() -> DOUBLE]</li><li>[newFct(x double)->double, {apackage.MyNewFunction}]</li></ul></td> </tr> + <tr><td colspan="5">Additional TAP Resources</td></tr> + <tr class="optional"> + <td class="done">additional_resources</td> + <td></td> + <td>text</td> + <td> + <p>Comma-separated list of additional TAP resources/end-point.</p> + <p> + By default, the following standard TAP resources are already existing: /sync, /async, /tables, /capabilities and /availability. + With this property, you can add a custom resource to your TAP service (e.g. /adqlValidator, /admin). + </p> + <p> + Each item of the list MUST be the name of a class implementing tap.resource.TAPResource. This class MUST have at least one constructor + with exactly one parameter of type tap.resource.TAP. + </p> + <p> + The string returned by tap.resource.TAPResource.getName() will be the resource name, following the root TAP service URL (e.g. if getName() + returns "foo", then its access URL will "{tapRoot}/foo"). Then, it is possible to replace TAP resources already existing by using the same + 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). + </p> + <p><em>By default, this list is empty ; only the standard TAP resources exist.</em></p> + </td> + <td>{aPackage.QuickADQLValidator}</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 c8e4b14d1e4970b0a734924af0035938e9459a16..0d5ad3803302f10078f3de259678725eced03b55 100644 --- a/src/tap/config/tap_full.properties +++ b/src/tap/config/tap_full.properties @@ -442,3 +442,23 @@ geometries = # # Default: no unknown function is allowed. udfs = + +######################## +# ADDITIONAL RESOURCES # +######################## + +# [OPTIONAL] +# Comma-separated list of additional TAP resources/end-point. +# +# By default, the following standard TAP resources are already existing: /sync, /async, /tables, /capabilities and /availability. +# With this property, you can add a custom resource to your TAP service (e.g. /adqlValidator, /admin). +# +# Each item of the list MUST be the name of a class implementing tap.resource.TAPResource. This class MUST have at least one constructor with +# exactly one parameter of type tap.resource.TAP. +# +# The string returned by tap.resource.TAPResource.getName() will be the resource name, following the root TAP service URL (e.g. if getName() +# returns "foo", then its access URL will "{tapRoot}/foo"). Then, it is possible to replace TAP resources already existing by using the same +# 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 diff --git a/src/tap/metadata/TAPSchema.java b/src/tap/metadata/TAPSchema.java index c43b44ec35ddd945ea04675cb64ff9ddbfdbcf3a..a7634434dc1a35812756844a4771186b826b5c2e 100644 --- a/src/tap/metadata/TAPSchema.java +++ b/src/tap/metadata/TAPSchema.java @@ -93,7 +93,7 @@ public class TAPSchema implements Iterable<TAPTable> { */ public TAPSchema(String schemaName){ if (schemaName == null || schemaName.trim().length() == 0) - throw new NullPointerException("Missing schema name !"); + throw new NullPointerException("Missing schema name!"); int indPrefix = schemaName.lastIndexOf('.'); adqlName = (indPrefix >= 0) ? schemaName.substring(indPrefix + 1).trim() : schemaName.trim(); dbName = adqlName; diff --git a/test/tap/config/TestConfigurableServiceConnection.java b/test/tap/config/TestConfigurableServiceConnection.java index a7534843dc44073b244ca77a77e8e93fa3ac1909..0712d9d2a16f3db534562394ca03820ec88c525c 100644 --- a/test/tap/config/TestConfigurableServiceConnection.java +++ b/test/tap/config/TestConfigurableServiceConnection.java @@ -392,7 +392,7 @@ public class TestConfigurableServiceConnection { fail("This MUST have failed because an incorrect File Manager value has been provided!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); - assertEquals("Unknown value for the property \"" + KEY_FILE_MANAGER + "\": \"foo\". Only two possible values: " + VALUE_LOCAL + " or a class path between {...}.", e.getMessage()); + assertEquals("Class name expected for the property \"file_manager\" instead of: \"foo\"! The specified class must extend/implement uws.service.file.UWSFileManager.", e.getMessage()); } // Custom log level and log rotation: diff --git a/test/tap/config/TestTAPConfiguration.java b/test/tap/config/TestTAPConfiguration.java index 5cf52c268b1c43b0554e73607fef5b1f36400711..a7d6390fc0a24471ae23bc75d70a20019eca298f 100644 --- a/test/tap/config/TestTAPConfiguration.java +++ b/test/tap/config/TestTAPConfiguration.java @@ -11,13 +11,23 @@ import static tap.config.TAPConfiguration.KEY_FILE_MANAGER; import static tap.config.TAPConfiguration.KEY_MAX_OUTPUT_LIMIT; import static tap.config.TAPConfiguration.fetchClass; import static tap.config.TAPConfiguration.isClassPath; +import static tap.config.TAPConfiguration.newInstance; import static tap.config.TAPConfiguration.parseLimit; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + import org.junit.Before; import org.junit.Test; import tap.ServiceConnection.LimitUnit; import tap.TAPException; +import tap.metadata.TAPMetadata; +import tap.metadata.TAPSchema; +import adql.query.ColumnReference; public class TestTAPConfiguration { @@ -138,6 +148,110 @@ public class TestTAPConfiguration { } } + @Test + public void testNewInstance(){ + // VALID CONSTRUCTOR with no parameters: + try{ + TAPMetadata metadata = newInstance("{tap.metadata.TAPMetadata}", "metadata", TAPMetadata.class); + assertNotNull(metadata); + assertEquals("tap.metadata.TAPMetadata", metadata.getClass().getName()); + }catch(Exception ex){ + ex.printStackTrace(); + fail("This test should have succeeded: the parameters of newInstance(...) are all valid."); + } + + // VALID CONSTRUCTOR with some parameters: + try{ + final String schemaName = "MySuperSchema", description = "And its less super description.", utype = "UTYPE"; + TAPSchema schema = newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{String.class,String.class,String.class}, new String[]{schemaName,description,utype}); + assertNotNull(schema); + assertEquals("tap.metadata.TAPSchema", schema.getClass().getName()); + assertEquals(schemaName, schema.getADQLName()); + assertEquals(description, schema.getDescription()); + assertEquals(utype, schema.getUtype()); + }catch(Exception ex){ + ex.printStackTrace(); + fail("This test should have succeeded: the constructor TAPSchema(String,String,String) exists."); + } + + // VALID CONSTRUCTOR with some parameters whose the type is an extension (not the exact type): + OutputStream output = null; + File tmp = new File("tmp.empty"); + try{ + output = newInstance("{java.io.BufferedOutputStream}", "stream", OutputStream.class, new Class<?>[]{OutputStream.class}, new OutputStream[]{new FileOutputStream(tmp)}); + assertNotNull(output); + assertEquals(BufferedOutputStream.class, output.getClass()); + }catch(Exception ex){ + ex.printStackTrace(); + fail("This test should have succeeded: the constructor TAPSchema(String,String,String) exists."); + }finally{ + try{ + tmp.delete(); + if (output != null) + output.close(); + }catch(IOException ioe){} + } + + // NOT A CLASS NAME: + try{ + TAPMetadata metadata = newInstance("tap.metadata.TAPMetadata", "metadata", TAPMetadata.class); + assertNotNull(metadata); + assertEquals("tap.metadata.TAPMetadata", metadata.getClass().getName()); + }catch(Exception ex){ + assertEquals(TAPException.class, ex.getClass()); + assertEquals("Class name expected for the property \"metadata\" instead of: \"tap.metadata.TAPMetadata\"! The specified class must extend/implement tap.metadata.TAPMetadata.", ex.getMessage()); + } + + // NO MATCHING CONSTRUCTOR: + try{ + newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{Integer.class}, new Object[]{new Integer(123)}); + }catch(Exception ex){ + assertEquals(TAPException.class, ex.getClass()); + assertEquals("Missing constructor tap.metadata.TAPSchema(java.lang.Integer)! See the value \"{tap.metadata.TAPSchema}\" of the property \"schema\".", ex.getMessage()); + } + + // VALID CONSTRUCTOR with primitive type: + try{ + ColumnReference colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{int.class}, new Object[]{123}); + assertNotNull(colRef); + assertEquals(ColumnReference.class, colRef.getClass()); + assertEquals(123, colRef.getColumnIndex()); + colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{int.class}, new Object[]{new Integer(123)}); + assertNotNull(colRef); + assertEquals(ColumnReference.class, colRef.getClass()); + assertEquals(123, colRef.getColumnIndex()); + }catch(Exception ex){ + ex.printStackTrace(); + fail("This test should have succeeded: the constructor ColumnReference(int) exists."); + } + + // WRONG CONSTRUCTOR with primitive type: + try{ + newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{Integer.class}, new Object[]{new Integer(123)}); + }catch(Exception ex){ + assertEquals(TAPException.class, ex.getClass()); + assertEquals("Missing constructor adql.query.ColumnReference(java.lang.Integer)! See the value \"{adql.query.ColumnReference}\" of the property \"colRef\".", ex.getMessage()); + } + + // THE CONSTRUCTOR THROWS AN EXCEPTION: + try{ + newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{String.class}, new Object[]{null}); + }catch(Exception ex){ + assertEquals(TAPException.class, ex.getClass()); + assertNotNull(ex.getCause()); + assertEquals(NullPointerException.class, ex.getCause().getClass()); + assertEquals("Missing schema name!", ex.getCause().getMessage()); + } + + // THE CONSTRUCTOR THROWS AN EXCEPTION: + try{ + newInstance("{tap.config.TestTAPConfiguration$ClassAlwaysThrowTAPError}", "tapError", ClassAlwaysThrowTAPError.class); + }catch(Exception ex){ + assertEquals(TAPException.class, ex.getClass()); + assertEquals("This error is always thrown by ClassAlwaysThrowTAPError ^^", ex.getMessage()); + } + } + /** * TEST parseLimit(String,String): * - nothing, -123, 0 => {-1,LimitUnit.rows} @@ -285,4 +399,11 @@ public class TestTAPConfiguration { return (ex.getCause() == null || ex.getMessage().equals(ex.getCause().getMessage())) ? ex.getMessage() : ex.getCause().getMessage(); } + private static class ClassAlwaysThrowTAPError { + @SuppressWarnings("unused") + public ClassAlwaysThrowTAPError() throws TAPException{ + throw new TAPException("This error is always thrown by ClassAlwaysThrowTAPError ^^"); + } + } + }