diff --git a/src/tap/metadata/TAPMetadata.java b/src/tap/metadata/TAPMetadata.java index 2ea1aaf8134665e71ee74b3dced210433adac873..7181bc5301b48df8d3c24755251a0fcda46ca8fc 100644 --- a/src/tap/metadata/TAPMetadata.java +++ b/src/tap/metadata/TAPMetadata.java @@ -16,7 +16,7 @@ package tap.metadata; * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -64,7 +64,7 @@ import uws.UWSToolBox; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (07/2016) + * @version 2.1 (03/2017) */ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResource { @@ -386,6 +386,32 @@ public class TAPMetadata implements Iterable<TAPSchema>, VOSIResource, TAPResour return tables; } + /** + * Get the description of the ObsCore table, if it is defined. + * + * <p> + * This function is case sensitive only on the schema name + * (i.e. <code>ivoa</code>) which must be defined in full lower case. + * The table name (i.e. <code>ObsCore</code>) will be found whatever + * the case it is written in. + * </p> + * + * @return Description of the ObsCore table, + * or <code>NULL</code> if this table is not provided by this TAP service. + * + * @since 2.1 + */ + public TAPTable getObsCoreTable(){ + TAPSchema ivoaSchema = getSchema("ivoa"); + if (ivoaSchema != null){ + for(TAPTable t : ivoaSchema){ + if (t.getADQLName().equalsIgnoreCase("obscore")) + return t; + } + } + return null; + } + /** * Get the number of all tables contained in this TAP metadata set. * diff --git a/src/tap/resource/TAP.java b/src/tap/resource/TAP.java index 8a528b61f750a6cec3d78f58b8bed7ff6bd116e7..e8fa13903ad6f9d88d473533f3cbf6b671c69a65 100644 --- a/src/tap/resource/TAP.java +++ b/src/tap/resource/TAP.java @@ -16,7 +16,7 @@ package tap.resource; * You should have received a copy of the GNU Lesser General Public License * along with TAPLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2016 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -30,6 +30,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import adql.db.DBColumn; import adql.db.FunctionDef; import tap.ServiceConnection; import tap.ServiceConnection.LimitUnit; @@ -38,6 +39,7 @@ import tap.error.DefaultTAPErrorWriter; import tap.formatter.OutputFormat; import tap.log.TAPLog; import tap.metadata.TAPMetadata; +import tap.metadata.TAPTable; import uk.ac.starlink.votable.VOSerializer; import uws.UWSException; import uws.UWSToolBox; @@ -53,7 +55,7 @@ import uws.service.log.UWSLog.LogLevel; * <p>At its creation it is creating and configuring the other resources in function of the given description of the TAP service.</p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.1 (08/2016) + * @version 2.1 (03/2017) */ public class TAP implements VOSIResource { @@ -489,6 +491,9 @@ public class TAP implements VOSIResource { xml.append("\t\t<accessURL use=\"base\">").append((getAccessURL() == null) ? "" : VOSerializer.formatText(getAccessURL())).append("</accessURL>\n"); xml.append("\t</interface>\n"); + // Data models: + appendDataModels(xml, "\t"); + // Language description: xml.append("\t<language>\n"); xml.append("\t\t<name>ADQL</name>\n"); @@ -623,6 +628,92 @@ public class TAP implements VOSIResource { return xml.toString(); } + /** + * List and declare all IVOA Data Models supported by this TAP service. + * + * <p>Currently, only the following DMs are natively supported:</p> + * <ul> + * <li>Obscore (1.0 and PR-1.1)</li> + * </ul> + * + * <p> + * More can be supported by extending this function + * (but not overwriting it completely otherwise the above + * supported DMs won't be anymore). + * </p> + * + * <p>A DM declaration should follow this XML syntax:</p> + * <pre><dataModel ivo-id="{DM-IVO_ID}">{DM-NAME}</dataModel></pre> + * + * @param xml The <code>/capabilities</code> in-progress content in which + * implemented DMs can be declared. + * @param linePrefix Tabulations/Spaces that should prefix all lines + * (for human readability). + * + * @since 2.1 + */ + protected void appendDataModels(final StringBuffer xml, final String linePrefix){ + appendObsCoreDM(xml, linePrefix); + } + + /** + * <p>Append the ObsCore DM declaration in the given {@link StringBuffer} + * if an <code>ivoa.Obscore</code> table can be found in <code>TAP_SCHEMA</code>.</p> + * + * <p> + * This function has no effect if <code>ivoa.Obscore</code> can not + * be found. The <code>ivoa</code> schema is searched case sensitively, + * but not the table name <code>Obscore</code> which can be written + * in any possible case. + * </p> + * + * <p> + * If an <code>ivoa.Obscore</code> table is found, this function + * detects automatically which version of Obscore is implemented. + * It will be declared as Obscore 1.1 if ALL the following columns + * are found (case INsensitively): <code>s_xel1</code>, <code>x_xel2</code>, + * <code>t_xel</code>, <code>em_xel</code> and <code>pol_xel</code>. + * If not, the Obscore table will be declared as Obscore 1.0. + * </p> + * + * @param xml The <code>/capabilities</code> in-progress content in which + * Obscore-DM should be declared if found. + * @param linePrefix Tabulations/Spaces that should prefix all lines + * (for human readability). + * + * @see TAPMetadata#getObsCoreTable() + * + * @since 2.1 + */ + protected void appendObsCoreDM(final StringBuffer xml, final String linePrefix){ + // Try to get the ObsCore table definition: + TAPTable obscore = service.getTAPMetadata().getObsCoreTable(); + + // If there is one, determine the supported DM version and declare it: + if (obscore != null){ + /* ObsCore 1.1 MUST have s_xel1, s_xel2, t_xel, em_xel and pol_xel + * These columns do not exist in ObsCore 1.0. */ + byte hasAllXel = 0x0; + for(DBColumn col : obscore){ + if (col.getADQLName().equalsIgnoreCase("s_xel1")) + hasAllXel |= 1; // 2^0 = 0000 0001 + else if (col.getADQLName().equalsIgnoreCase("s_xel2")) + hasAllXel |= 2; // 2^1 = 0000 0010 + else if (col.getADQLName().equalsIgnoreCase("t_xel")) + hasAllXel |= 4; // 2^2 = 0000 0100 + else if (col.getADQLName().equalsIgnoreCase("em_xel")) + hasAllXel |= 8; // 2^3 = 0000 1000 + else if (col.getADQLName().equalsIgnoreCase("pol_xel")) + hasAllXel |= 16; // 2^4 = 0001 0000 + } + // Finally add the appropriate DM declaration: + if (hasAllXel == 31) // 2^5 - 1 = 0001 1111 + xml.append(linePrefix + "<dataModel ivo-id=\"ivo://ivoa.net/std/ObsCore#core-1.1\">ObsCore-1.1</dataModel>\n"); + else + xml.append(linePrefix + "<dataModel ivo-id=\"ivo://ivoa.net/std/ObsCore/v1.0\">ObsCore-1.0</dataModel>\n"); + } + } + /* ************************************* */ /* MANAGEMENT OF THIS RESOURCE'S CONTENT */ /* ************************************* */ diff --git a/test/tap/formatter/ServiceConnection4Test.java b/test/tap/formatter/ServiceConnection4Test.java index 074448adc7051023bd40451f441a52e3094d0e17..0b55f7851233c44e686f1498b2eb9d25fa82df5c 100644 --- a/test/tap/formatter/ServiceConnection4Test.java +++ b/test/tap/formatter/ServiceConnection4Test.java @@ -3,16 +3,27 @@ package tap.formatter; import java.util.Collection; import java.util.Iterator; +import adql.db.FunctionDef; import tap.ServiceConnection; import tap.TAPFactory; import tap.log.TAPLog; import tap.metadata.TAPMetadata; import uws.service.UserIdentifier; import uws.service.file.UWSFileManager; -import adql.db.FunctionDef; public class ServiceConnection4Test implements ServiceConnection { + private TAPMetadata metadata = null; + private TAPFactory factory = null; + private UWSFileManager fileManager = null; + + public ServiceConnection4Test(){} + + public ServiceConnection4Test(final TAPMetadata metadata, final UWSFileManager fileManager){ + this.metadata = metadata; + this.fileManager = fileManager; + } + @Override public int[] getOutputLimit(){ return new int[]{1000000,1000000}; @@ -80,7 +91,7 @@ public class ServiceConnection4Test implements ServiceConnection { @Override public TAPMetadata getTAPMetadata(){ - return null; + return metadata; } @Override @@ -105,12 +116,16 @@ public class ServiceConnection4Test implements ServiceConnection { @Override public TAPFactory getFactory(){ - return null; + return factory; + } + + public void setFactory(TAPFactory factory){ + this.factory = factory; } @Override public UWSFileManager getFileManager(){ - return null; + return fileManager; } @Override diff --git a/test/tap/metadata/TestTAPMetadata.java b/test/tap/metadata/TestTAPMetadata.java new file mode 100644 index 0000000000000000000000000000000000000000..8979e878d09d5086ba34a90ba21a66161bae86a9 --- /dev/null +++ b/test/tap/metadata/TestTAPMetadata.java @@ -0,0 +1,46 @@ +package tap.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +import tap.metadata.TAPTable.TableType; + +public class TestTAPMetadata { + + @Test + public void testGetObsCoreTable(){ + TAPMetadata metadata = new TAPMetadata(); + + // CASE: no IVOA schema: + assertNull(metadata.getObsCoreTable()); + + // CASE: empty IVOA schema: + TAPSchema ivoaSchema = new TAPSchema("ivoa"); + metadata.addSchema(ivoaSchema); + assertNull(metadata.getObsCoreTable()); + + // CASE: with ObsCore table as defined in the ObsCore's IVOA standard: + TAPTable obscore = new TAPTable("ObsCore"); + ivoaSchema.addTable(obscore); + assertNotNull(metadata.getObsCoreTable()); + assertEquals("ivoa.ObsCore", metadata.getObsCoreTable().getFullName()); + + // CASE: with "obscore" (all lower-case): + obscore = new TAPTable("obscore", TableType.view); + ivoaSchema.removeAllTables(); + ivoaSchema.addTable(obscore); + assertNotNull(metadata.getObsCoreTable()); + assertEquals("ivoa.obscore", metadata.getObsCoreTable().getFullName()); + + // CASE: ObsCore table BUT in a different schema: + metadata.removeAllSchemas(); + TAPSchema differentSchema = new TAPSchema("different"); + metadata.addSchema(differentSchema); + differentSchema.addTable("ObsCore"); + assertNull(metadata.getObsCoreTable()); + } + +} diff --git a/test/tap/resource/TestTAP.java b/test/tap/resource/TestTAP.java new file mode 100644 index 0000000000000000000000000000000000000000..b8b291542917577ef80c413fe9ccea3cb1afe4f7 --- /dev/null +++ b/test/tap/resource/TestTAP.java @@ -0,0 +1,91 @@ +package tap.resource; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.File; + +import org.junit.Test; + +import adql.db.DBType; +import adql.db.DBType.DBDatatype; +import tap.AbstractTAPFactory; +import tap.ServiceConnection; +import tap.TAPException; +import tap.db.DBConnection; +import tap.formatter.ServiceConnection4Test; +import tap.metadata.TAPMetadata; +import tap.metadata.TAPSchema; +import tap.metadata.TAPTable; +import uws.service.file.LocalUWSFileManager; + +public class TestTAP { + + @Test + public void testAppendObsCoreDM(){ + // Create the TAPMetadata: + TAPMetadata metadata = new TAPMetadata(); + TAPSchema ivoaSchema = new TAPSchema("ivoa"); + TAPTable obscore = new TAPTable("ObsCore"); + + // Create a TAP instance: + try{ + ServiceConnection4Test serviceConn = new ServiceConnection4Test(metadata, new LocalUWSFileManager(new File(System.getProperty("java.io.tmpdir")))); + serviceConn.setFactory(new TAPFactory4Test(serviceConn)); + TAP tap = new TAP(serviceConn); + StringBuffer xml = new StringBuffer(); + + // CASE: no Obscore table: + tap.appendObsCoreDM(xml, ""); + assertEquals(0, xml.length()); + + // CASE: with an IVOA schema: + metadata.addSchema(ivoaSchema); + tap.appendObsCoreDM(xml, ""); + assertEquals(0, xml.length()); + + // CASE: with an Obscore table (with no *_xel columns) - ObsCore 1.0: + ivoaSchema.addTable(obscore); + tap.appendObsCoreDM(xml, "\t"); + assertEquals("\t<dataModel ivo-id=\"ivo://ivoa.net/std/ObsCore/v1.0\">ObsCore-1.0</dataModel>\n", xml.toString()); + + // CASE: with an Obscore 1.1 table but not with all *_xel columns: + obscore.addColumn("s_xel1", new DBType(DBDatatype.BIGINT), null, null, null, null); + obscore.addColumn("s_xel2", new DBType(DBDatatype.BIGINT), null, null, null, null); + obscore.addColumn("t_xel", new DBType(DBDatatype.BIGINT), null, null, null, null); + obscore.addColumn("em_xel", new DBType(DBDatatype.BIGINT), null, null, null, null); + xml.delete(0, xml.length()); + tap.appendObsCoreDM(xml, "\t"); + assertEquals("\t<dataModel ivo-id=\"ivo://ivoa.net/std/ObsCore/v1.0\">ObsCore-1.0</dataModel>\n", xml.toString()); + + // CASE: correct Obscore 1.1 table: + obscore.addColumn("pol_xel", new DBType(DBDatatype.BIGINT), null, null, null, null); + xml.delete(0, xml.length()); + tap.appendObsCoreDM(xml, "\t"); + assertEquals("\t<dataModel ivo-id=\"ivo://ivoa.net/std/ObsCore#core-1.1\">ObsCore-1.1</dataModel>\n", xml.toString()); + + }catch(Exception e){ + e.printStackTrace(); + fail("Unexpected error while creating a TAP instance! (see console for more details)"); + } + } + + private static class TAPFactory4Test extends AbstractTAPFactory { + + protected TAPFactory4Test(ServiceConnection service) throws NullPointerException{ + super(service); + } + + @Override + public DBConnection getConnection(String jobID) throws TAPException{ + return null; + } + + @Override + public void freeConnection(DBConnection conn){} + + @Override + public void destroy(){} + } + +}